Service Authentication and Users
Tests of authentication, login and logout, role grants and the user-facing security services.
Authentication
AuthenticationAccountStateTests
Jube.Test.Service.Authentication.AuthenticationAccountStateTests — 26 tests. Category: Service. Collection: Database.
| Test | Kind | Verifies |
|---|---|---|
DeletedUser_IsIndistinguishableFromUnknown_AndDoesNoHashWorkAsync |
Fact | Deleted user is indistinguishable from unknown and does no hash work. |
NotActiveUser_IsUnauthorised_FailureType2_WithoutHashWorkAsync |
Theory | Not active user is unauthorised failure type2 without hash work. |
NullActiveFlag_IsTreatedAsInactiveAsync |
Fact | Null active flag is treated as inactive. |
LockedUser_CannotLogIn_EvenWithTheCorrectPassword_FailureType3Async |
Fact | Locked user cannot log in even with the correct password failure type3. |
NullPasswordLockedFlag_IsTreatedAsUnlockedAsync |
Fact | Null password locked flag is treated as unlocked. |
ExpiredPassword_WithTheCorrectPassword_IsForbidden_AndNotRecordedInTheLoginHistoryAsync |
Fact | Expired password with the correct password is forbidden and not recorded in the login history. |
NullPasswordExpiry_WithTheCorrectPassword_IsForbiddenAsync |
Fact | Null password expiry with the correct password is forbidden. |
PasswordExpiryBoundary_IsInclusive_OnTheInjectedClockAsync |
Fact | Password expiry boundary is inclusive on the injected clock. |
ExpiredPassword_WithAWrongPassword_IsJustBadCredentials_NotForbiddenAsync |
Fact | Expired password with a wrong password is just bad credentials not forbidden. |
ASuccessfulLogin_DoesNotChangeAnyOtherUserAsync |
Fact | A successful login does not change any other user. |
UserWithNoTenantMembership_CanStillAuthenticateAsync |
Fact | User with no tenant membership can still authenticate. |
UserInBothTenants_AuthenticatesOnceAsync |
Fact | User in both tenants authenticates once. |
UserOfTenantB_AuthenticatesWithoutTenantContextAsync |
Fact | User of tenant b authenticates without tenant context. |
UserOfADisabledTenant_CanStillAuthenticateAsync |
Theory | User of a disabled tenant can still authenticate. |
UserWhoseRoleIsDeleted_CanStillAuthenticateAsync |
Fact | User whose role is deleted can still authenticate. |
ACorruptOrMissingStoredHash_NeverAuthenticates_UnderTheRealArgon2SchemeAsync |
Theory | A corrupt or missing stored hash never authenticates under the real argon2 scheme. |
TwoUsersWithDifferentPasswords_CannotUseEachOthersPasswordAsync |
Fact | Two users with different passwords cannot use each others password. |
EveryNonSuccessReason_ProducesTheSameOutcomeShape_ForAUserWithAnExpiryAsync |
Theory | Every non success reason produces the same outcome shape for a user with an expiry. |
EveryBranchDoesExactlyOneHashVerification_SoTimingDoesNotEnumerateUsersAsync |
Theory | Every branch does exactly one hash verification so timing does not enumerate users. |
AnEmptyPasswordForARealAccount_StillDoesOneDummyHashVerification_CR09Async |
Theory | An empty password for a real account still does one dummy hash verification CR 09. |
TheDummyVerification_CanNeverAuthenticateEvenWithTheDummyPasswordItselfAsync |
Theory | The dummy verification can never authenticate even with the dummy password itself. |
TheWireResponsesAndHistoryShapeStayIdentical_AcrossTheBranchesAsync |
Fact | The wire responses and history shape stay identical across the branches. |
Weakness_AWrongPasswordForAUserWithoutExpiry_IsForbiddenNotUnauthorised_AnEnumerationOracleAsync |
Fact | Weakness a wrong password for a user without expiry is forbidden not unauthorised an enumeration oracle. |
Weakness_AUserWithoutPasswordCreatedDate_IsAlsoAnOracleAsync |
Fact | Weakness a user without password created date is also an oracle. |
Weakness_TheCorrectPasswordForAnExpiredUser_IsConfirmedByA403Async |
Fact | Weakness the correct password for an expired user is confirmed by a 403. |
UnknownUser_AndWrongPassword_BothWriteOneFailedHistoryRow_WithDifferentInternalFailureTypesAsync |
Fact | Unknown user and wrong password both write one failed history row with different internal failure types. |
AuthenticationCookieTests
Jube.Test.Service.Authentication.AuthenticationCookieTests — 24 tests. Category: Service.
| Test | Kind | Verifies |
|---|---|---|
TheCookieFlags_FollowSessionAndSecureSettings |
Theory | The cookie flags follow session and secure settings. |
StrictCookieFalse_IssuesLaxWhenSecure |
Fact | Strict cookie false issues lax when secure. |
StrictCookieDefault_IssuesStrictWhenSecure |
Fact | Strict cookie default issues strict when secure. |
StrictCookieFalse_DeletesWithLaxWhenSecure |
Fact | Strict cookie false deletes with lax when secure. |
TheDefaultEnvironment_IsOneSessionCookie_NotSecure_AndHttpOnly |
Fact | The default environment is one session cookie not secure and http only. |
TheCookieValue_IsTheToken_AndTheExpiryTravelsInAHeaderNotACookie |
Fact | The cookie value is the token and the expiry travels in a header not a cookie. |
TheJsonBody_HasExactlyTokenAndExpiration |
Fact | The json body has exactly token and expiration. |
TheIssuedToken_IsAcceptedByTheRealHandler_AsTheUser_ViaBearerAndCookieAsync |
Fact | The issued token is accepted by the real handler as the user via bearer and cookie. |
TheTokenHeaderAndPayload_CarryOnlyTheNameIssuerAudienceAndExpiry |
Fact | The token header and payload carry only the name issuer audience and expiry. |
Weakness_TheSameSecond_YieldsTheIdenticalToken_ThereIsNoJtiOrIssuedAt |
Fact | Weakness the same second yields the identical token there is no jti or issued at. |
NamesSurviveTheTokenAndTheCookieRoundTrip_ExactlyAsync |
Theory | Names survive the token and the cookie round trip exactly. |
ATokenIssuedForAnEmptyName_IsRejectedByTheHandlerAsMalformedAsync |
Fact | A token issued for an empty name is rejected by the handler as malformed. |
AJwtKeyBelow128Bits_CannotSign_SoLoginWouldFailAt500 |
Theory | A jwt key below128 bits cannot sign so login would fail at500. |
ATamperedToken_IsRejectedAsync |
Theory | A tampered token is rejected. |
AStructurallyMalformedToken_IsSimplyNotAuthenticated_NeverAnExceptionAsync |
Theory | A structurally malformed token is simply not authenticated never an exception. |
ATokenSignedWithADifferentKey_IsRejectedAsync |
Fact | A token signed with a different key is rejected. |
ATokenForAnotherIssuerOrAudience_IsRejectedAsync |
Theory | A token for another issuer or audience is rejected. |
ExpiryIsEnforced_With30SecondsOfClockSkewAsync |
Theory | Expiry is enforced with30 seconds of clock skew. |
AnExpiredTokenInTheCookie_IsRejectedTooAsync |
Fact | An expired token in the cookie is rejected too. |
AnEmptyCookie_IsNoCredentialsAtAll_NotAFailureAsync |
Fact | An empty cookie is no credentials at all not a failure. |
AnAlteredExpiryHeader_DoesNotExtendTheTokensLifeAsync |
Fact | An altered expiry header does not extend the tokens life. |
TheJwtSecurityTokenHandler_ReadsTheSameClaimsThatWereIssued |
Fact | The jwt security token handler reads the same claims that were issued. |
AnApiKeyAuthenticatedRequest_IsNeverIssuedARefreshedSessionCookieAsync |
Fact | An api key authenticated request is never issued a refreshed session cookie. |
AJwtCookieAuthenticatedRequest_StillGetsARefreshedSessionCookieAsync |
Fact | A jwt cookie authenticated request still gets a refreshed session cookie. |
AuthenticationEndpointTests
Jube.Test.Service.Authentication.AuthenticationEndpointTests — 50 tests. Category: Service. Collection: Database.
| Test | Kind | Verifies |
|---|---|---|
ExactlyTheFiveLegacyRoutesAndLogout_AreMapped_WithTheirVerbsAsync |
Fact | Exactly the five legacy routes and logout are mapped with their verbs. |
AnonymousEndpointsStayAnonymous_AndTheOthersStayProtectedAsync |
Theory | Anonymous endpoints stay anonymous and the others stay protected. |
TheNegotiateRoutes_AreBoundToTheNegotiateSchemeOnlyAsync |
Theory | The negotiate routes are bound to the negotiate scheme only. |
ChangePassword_UsesTheDefaultSchemeAuthorisationAsync |
Fact | Change password uses the default scheme authorisation. |
NothingInTheAuthenticationAreaIsExposedToTheAgentToolCatalogue |
Fact | Nothing in the authentication area is exposed to the agent tool catalogue. |
WrongVerbs_Are405_AndReachNoLogicAsync |
Theory | Wrong verbs are405 and reach no logic. |
UnknownRoutes_Are404Async |
Theory | Unknown routes are404. |
RoutingIsCaseInsensitive_AsTheLegacyControllerRoutesWereAsync |
Fact | Routing is case insensitive as the legacy controller routes were. |
ACorrectLogin_Is200_WithATokenBodyAndExactlyOneHttpOnlyCookieAsync |
Fact | A correct login is200 with a token body and exactly one http only cookie. |
TheIssuedToken_AuthenticatesLaterRequests_ViaBearerAndViaCookieAsync |
Fact | The issued token authenticates later requests via bearer and via cookie. |
ARejectedToken_CannotReachChangePasswordAsync |
Theory | A rejected token cannot reach change password. |
AMalformedBearerToken_Is401OnProtectedRoutes_AndAnonymousRoutesProceedAsAnonymousAsync |
Theory | A malformed bearer token is401 on protected routes and anonymous routes proceed as anonymous. |
AMalformedCookieToken_IsTreatedTheSameAsync |
Theory | A malformed cookie token is treated the same. |
AnUnacceptableToken_NeverBreaksAnAnonymousRoute_AndLeaksNoExceptionDetailAsync |
Theory | An unacceptable token never breaks an anonymous route and leaks no exception detail. |
NoCredentialsAtAll_On_ChangePassword_Is401WithAnEmptyBodyAsync |
Fact | No credentials at all on change password is401 with an empty body. |
EveryFailedLoginReason_LooksIdenticalOnTheWireAsync |
Theory | Every failed login reason looks identical on the wire. |
Weakness_TheWireDiffersForAUserWithoutAPasswordExpiry_403VersusTheUsual401Async |
Fact | Weakness the wire differs for a user without a password expiry 403 versus the usual401. |
AnExpiredPassword_IsA403_WithoutCookiesAsync |
Fact | An expired password is a 403 without cookies. |
ANewStrongPassword_WithTheOldOne_Is200_AndTheNewOneWorksAsync |
Fact | A new strong password with the old one is200 and the new one works. |
AWeakNewPassword_Is400_WithTheErrorsArrayTheUiReadsAsync |
Fact | A weak new password is400 with the errors array the ui reads. |
ValidationFailures_Are400_WithABodyMatchingTheLegacyNewtonsoftShapeAsync |
Theory | Validation failures are400 with a body matching the legacy newtonsoft shape. |
TheSuccessBody_MatchesTheLegacyNewtonsoftShapeAsync |
Fact | The success body matches the legacy newtonsoft shape. |
WhenAnotherSchemeIsActive_LoginAndTheWireProbe_Are404Async |
Theory | When another scheme is active login and the wire probe are404. |
AnAlreadyAuthenticatedCaller_GetsATokenForTheCredentialsSubmitted_NotForTheCallerAsync |
Fact | An already authenticated caller gets a token for the credentials submitted not for the caller. |
ParallelLoginsOverHttp_AllGetTheirAnswers_AndWrongOnesNeverSucceedAsync |
Fact | Parallel logins over http all get their answers and wrong ones never succeed. |
WirePasswordHash_Is200_WithTheFlagInAJsonBodyAsync |
Theory | Wire password hash is200 with the flag in a json body. |
WirePasswordHash_UnknownAndMissingNamesAsync |
Theory | Wire password hash unknown and missing names. |
ChangePassword_WithAValidToken_Is200WithAnEmptyBody_AndChangesTheHashAsync |
Fact | Change password with a valid token is200 with an empty body and changes the hash. |
ChangePassword_WithTheWrongCurrentPassword_Is401Async |
Fact | Change password with the wrong current password is401. |
ChangePassword_WeakNew_Is400_WithTheErrorsArrayAsync |
Fact | Change password weak new is400 with the errors array. |
ChangePassword_IsForbidden_WhenNegotiateOwnsIdentityAsync |
Fact | Change password is forbidden when negotiate owns identity. |
ChangePassword_ForAValidTokenWhoseUserNoLongerExists_Is401WithNoDetailAsync |
Fact | Change password for a valid token whose user no longer exists is401 with no detail. |
TheLoginHistoryRemoteIp_IsTheConnectionAddress_NeverTheBodyOrAForwardedHeaderAsync |
Fact | The login history remote ip is the connection address never the body or a forwarded header. |
MalformedJsonOrWrongShapes_Are400_AndTouchNothingAsync |
Theory | Malformed json or wrong shapes are400 and touch nothing. |
AnEmptyOrNullBody_Is400_WithNoDetail_AndTouchesNothingAsync |
Theory | An empty or null body is400 with no detail and touches nothing. |
ChangePassword_WithNoRecognisedProperties_Is400_NotA500Async |
Fact | Change password with no recognised properties is400 not a 500. |
WirePasswordHash_IgnoresUnrecognisedProperties_AndAnswersFalseForNoUserNameAsync |
Fact | Wire password hash ignores unrecognised properties and answers false for no user name. |
ANonJsonContentType_Is415_AndTouchesNothingAsync |
Theory | A non json content type is415 and touches nothing. |
APostWithNoBodyAtAll_Is400_WithoutTouchingTheDatabaseAsync |
Fact | A post with no body at all is400 without touching the database. |
UnknownAndExtraJsonProperties_AreIgnored_AndCaseInsensitiveNamesBindAsync |
Fact | Unknown and extra json properties are ignored and case insensitive names bind. |
AHugeBody_OfTwoMegabytes_IsRejectedOrFailsClosed_NeverAuthenticatingAsync |
Fact | A huge body of two megabytes is rejected or fails closed never authenticating. |
ByNegotiate_WithAnEstablishedIdentity_Is200_WithTheSessionCookieAsync |
Fact | By negotiate with an established identity is200 with the session cookie. |
ByNegotiate_WithoutAnIdentity_ChallengesWith401AndWwwAuthenticateNegotiateAsync |
Theory | By negotiate without an identity challenges with401 and www authenticate negotiate. |
ByNegotiate_AnUnknownIdentity_Is403Async |
Fact | By negotiate an unknown identity is403. |
ByNegotiate_WhenNegotiateIsNotEnabled_Is404_EvenWithAnIdentityHeaderAsync |
Fact | By negotiate when negotiate is not enabled is404 even with an identity header. |
ByNegotiate_TheHybridJwtIsNotAcceptedInPlaceOfANegotiateIdentityAsync |
Fact | By negotiate the hybrid jwt is not accepted in place of a negotiate identity. |
ByNegotiate_WithMfaEnabled_Is202WithAnEmptyBody_AndThePostFlowCompletesAsync |
Fact | By negotiate with mfa enabled is202 with an empty body and the post flow completes. |
WhenTheNegotiateSchemeIsNotRegistered_TheRoutesFailWith500NotAnAccidentalOpenAsync |
Fact | When the negotiate scheme is not registered the routes fail with500 not an accidental open. |
UserNamePassword_WithMfa_RightCode200_WrongCode401_MissingCode400Async |
Fact | User name password with mfa right code200 wrong code401 missing code400. |
AProviderOutage_DuringMfa_Is500_AndNeverAnAuthenticationAsync |
Fact | A provider outage during mfa is500 and never an authentication. |
AuthenticationLockoutTests
Jube.Test.Service.Authentication.AuthenticationLockoutTests — 27 tests. Category: Service. Collection: Database.
| Test | Kind | Verifies |
|---|---|---|
TheAccountLocksExactlyOnTheNthWrongPassword_WhereNIsPasswordAttemptsAsync |
Theory | The account locks exactly on the nth wrong password where n is password attempts. |
WithTheDefaultOfThree_TheThirdWrongPasswordLocks_NotTheFifthAsync |
Fact | With the default of three the third wrong password locks not the fifth. |
ANonPositiveThreshold_LocksOnTheVeryFirstFailure_AndNeverVerifiesAgainstTheRealHashTwiceAsync |
Theory | A non positive threshold locks on the very first failure and never verifies against the real hash twice. |
OnceLocked_EvenTheCorrectPasswordIsRefused_WithNoRealHashWork_AndTheCounterStopsMovingAsync |
Fact | Once locked even the correct password is refused with no real hash work and the counter stops moving. |
ALockoutDoesNotExpireBeforeTheConfiguredWindowAsync |
Fact | A lockout does not expire before the configured window. |
ALockoutAutomaticallyExpiresAfterTheConfiguredWindowAsync |
Fact | A lockout automatically expires after the configured window. |
ANonPositiveLockoutMinutes_NeverAutoExpiresNoMatterHowOldTheLockIsAsync |
Theory | A non positive lockout minutes never auto expires no matter how old the lock is. |
EnablePasswordLockoutResetFalse_NeverAutoExpiresEvenWithAPositiveWindowAsync |
Fact | Enable password lockout reset false never auto expires even with a positive window. |
EnablePasswordLockoutResetTrue_StillAutoExpiresAfterTheConfiguredWindowAsync |
Fact | Enable password lockout reset true still auto expires after the configured window. |
ASuccessfulLogin_ResetsTheFailureCounterToZeroAsync |
Fact | A successful login resets the failure counter to zero. |
AfterAReset_TheCountRestartsFromZero_SoFailuresDoNotAccumulateAcrossSuccessesAsync |
Fact | After a reset the count restarts from zero so failures do not accumulate across successes. |
TheCorrectPasswordOnTheLastUnlockedAttempt_StillSucceedsAsync |
Fact | The correct password on the last unlocked attempt still succeeds. |
ExpiredPasswordWithTheCorrectPassword_ClearsTheCounter_SoARightPasswordNeverBuildsUpToALockAsync |
Fact | Expired password with the correct password clears the counter so a right password never builds up to a lock. |
AUserWithoutAPasswordExpiry_IsCountedAndLockedLikeAnyOther_ButStillGetsTheMustChangeOutcomeAsync |
Fact | A user without a password expiry is counted and locked like any other but still gets the must change outcome. |
ACorrectPasswordForAUserWithoutExpiry_StillGetsMustChange_AndIsNotCountedAsync |
Fact | A correct password for a user without expiry still gets must change and is not counted. |
TheNoExpiryLockThresholdMatchesTheExpiringUsersThresholdAsync |
Theory | The no expiry lock threshold matches the expiring users threshold. |
ANullCreatedDateUserIsLockedTooAsync |
Fact | A null created date user is locked too. |
WhenANewPasswordIsSupplied_TheNoExpiryUserDoesLockOutAsync |
Fact | When a new password is supplied the no expiry user does lock out. |
CountersAreIndependentPerUserAsync |
Fact | Counters are independent per user. |
ParallelWrongPasswords_LockAtTheBudget_AndOnlyTheAllowedNumberEverReachTheRealHashAsync |
Theory | Parallel wrong passwords lock at the budget and only the allowed number ever reach the real hash. |
ParallelFailuresBelowTheThreshold_ProduceExactlyNCountedFailures_NoLostUpdatesAsync |
Theory | Parallel failures below the threshold produce exactly n counted failures no lost updates. |
ParallelGuesses_IncludingTheRightPassword_CannotAllBeTried_TheBudgetIsEnforcedAsync |
Fact | Parallel guesses including the right password cannot all be tried the budget is enforced. |
ParallelWrongPasswords_NeverExceedTheThresholdOfRealVerifications_EvenWithAHigherThresholdAsync |
Fact | Parallel wrong passwords never exceed the threshold of real verifications even with a higher threshold. |
ParallelCorrectPasswordsOnALockedAccount_NeverSucceedAsync |
Fact | Parallel correct passwords on a locked account never succeed. |
ParallelMixOfRightAndWrong_OnlyTheRightOnesCanSucceed_AndNoneAreForgedAsync |
Fact | Parallel mix of right and wrong only the right ones can succeed and none are forged. |
ParallelFailuresAgainstDifferentUsers_AreCountedExactlyAndDoNotBleedAsync |
Fact | Parallel failures against different users are counted exactly and do not bleed. |
SequentialLoginsAfterAParallelBurst_BehaveAsLockedAsync |
Fact | Sequential logins after a parallel burst behave as locked. |
AuthenticationLogoutServiceTests
Jube.Test.Service.Authentication.AuthenticationLogoutServiceTests — 11 tests. Category: Service. Collection: Database.
| Test | Kind | Verifies |
|---|---|---|
AValidSession_RevokesTheUsersTokens_ClosesTheirHubsAndRecordsTheRowAsync |
Fact | A valid session revokes the users tokens closes their hubs and records the row. |
NoSession_RevokesNothing_ClosesNothing_AndNeverThrowsAsync |
Fact | No session revokes nothing closes nothing and never throws. |
AnAuthenticatedIdentityWithoutANameIsNoSessionAsync |
Theory | An authenticated identity without a name is no session. |
ADatabaseFailure_StillEndsTheBrowserSession_LogsAnError_AndClosesTheHubsAsync |
Fact | A database failure still ends the browser session logs an error and closes the hubs. |
AFailingHubClose_IsLogged_AndDoesNotFailTheLogoutAsync |
Fact | A failing hub close is logged and does not fail the logout. |
TheAuditRow_IsBestEffort_AFailingInsertNeverChangesTheOutcomeAsync |
Fact | The audit row is best effort a failing insert never changes the outcome. |
ALogoutIsAuditedWithItsOutcomeAndSpanAsync |
Fact | A logout is audited with its outcome and span. |
TheActorOfTheAuditLineIsNeverControlCharactersOfTheNameAsync |
Fact | The actor of the audit line is never control characters of the name. |
NoSessionRows_AreWrittenOncePerAddressPerMinute_ButAlwaysForASessionAsync |
Fact | No session rows are written once per address per minute but always for a session. |
TheRecorder_ClipsLengthAndStripsControlCharactersAsync |
Fact | The recorder clips length and strips control characters. |
TheRecorder_NeverThrows_WhateverTheDatabaseDoesAsync |
Fact | The recorder never throws whatever the database does. |
AuthenticationMfaTests
Jube.Test.Service.Authentication.AuthenticationMfaTests — 30 tests. Category: Service. Collection: Database.
| Test | Kind | Verifies |
|---|---|---|
MfaRequired_ABlankCodeFailsValidation_BeforeAnyCredentialIsCheckedAsync |
Theory | Mfa required a blank code fails validation before any credential is checked. |
MfaRequired_TheCorrectCode_IssuesTheCookies_AndTheProviderSawTheUserAndCodeAsync |
Fact | Mfa required the correct code issues the cookies and the provider saw the user and code. |
MfaRequired_AWrongCode_IsUnauthorised_AndIssuesNothingAsync |
Fact | Mfa required a wrong code is unauthorised and issues nothing. |
AWrongMfaCode_IsCountedAsAFailure_AndDoesNotCommitTheSuccessOrResetTheCounterAsync |
Fact | A wrong mfa code is counted as a failure and does not commit the success or reset the counter. |
ACorrectMfaCode_CompletesTheLogin_WritesTheSuccessRecord_AndResetsTheCounterAsync |
Fact | A correct mfa code completes the login writes the success record and resets the counter. |
MfaGuessing_LocksTheAccountOnTheThirdWrongCode_AndALockedAccountRejectsTheRightCodeAndPasswordAsync |
Fact | Mfa guessing locks the account on the third wrong code and a locked account rejects the right code and password. |
AlternatingRightPasswordAndWrongCode_CannotDodgeTheCounterAsync |
Fact | Alternating right password and wrong code cannot dodge the counter. |
MixedPasswordAndMfaFailures_ShareOneCounterAsync |
Fact | Mixed password and mfa failures share one counter. |
AProviderOutage_GivesTheReservedAttemptBack_SoItIsNotCountedAgainstTheUserAsync |
Fact | A provider outage gives the reserved attempt back so it is not counted against the user. |
NegotiateMfa_WrongCodesAreCounted_TheAccountLocksOnTheThird_AndEverythingThenRefusesAsync |
Fact | Negotiate mfa wrong codes are counted the account locks on the third and everything then refuses. |
NegotiateMfa_ParallelWrongCodes_NeverReachTheProviderMoreThanTheBudgetAllowsAsync |
Fact | Negotiate mfa parallel wrong codes never reach the provider more than the budget allows. |
NegotiateMfa_ACorrectCodeResetsTheCounterAsync |
Fact | Negotiate mfa a correct code resets the counter. |
NegotiateMfa_ABlankCodeIsNotCountedAsync |
Fact | Negotiate mfa a blank code is not counted. |
MfaRequired_AWrongPassword_NeverReachesTheProviderAsync |
Fact | Mfa required a wrong password never reaches the provider. |
MfaRequired_AnAccountThatCannotLogIn_NeverReachesTheProviderAsync |
Theory | Mfa required an account that cannot log in never reaches the provider. |
MfaRequired_ACodeCannotBeReusedAsync |
Fact | Mfa required a code cannot be reused. |
MfaRequired_AnExpiredCode_IsRejected_OnTheInjectedClockAsync |
Fact | Mfa required an expired code is rejected on the injected clock. |
MfaRequired_ACodeIssuedForAnotherUser_IsRejectedAsync |
Fact | Mfa required a code issued for another user is rejected. |
MfaRequired_UnusualCodes_ArePassedToTheProviderVerbatim_AndRejectedAsync |
Theory | Mfa required unusual codes are passed to the provider verbatim and rejected. |
MfaRequired_AProviderFailure_PropagatesAsAnException_NeverAsASuccessAsync |
Fact | Mfa required a provider failure propagates as an exception never as a success. |
MfaRequired_TheCancellationTokenReachesTheProviderAsync |
Fact | Mfa required the cancellation token reaches the provider. |
MfaNotRequired_ACodeIsIgnored_AndTheProviderIsNeverCalledAsync |
Fact | Mfa not required a code is ignored and the provider is never called. |
MfaRequired_TheSubjectIsTheAuthenticatedIdentity_WhenOneIsPresentAsync |
Fact | Mfa required the subject is the authenticated identity when one is present. |
NegotiateWithMfa_TheGetSignalsMfaRequired_WithoutIssuingAnythingAsync |
Fact | Negotiate with mfa the get signals mfa required without issuing anything. |
NegotiateWithMfa_TheMfaPostWithoutACode_IsAccepted202_NotAnErrorAsync |
Fact | Negotiate with mfa the mfa post without a code is accepted202 not an error. |
NegotiateWithMfa_TheCorrectCode_IssuesCookiesForTheWindowsIdentityAsync |
Fact | Negotiate with mfa the correct code issues cookies for the windows identity. |
NegotiateWithMfa_AWrongCode_IsUnauthorised_AndAReusedCodeFailsAsync |
Fact | Negotiate with mfa a wrong code is unauthorised and a reused code fails. |
NegotiateWithMfa_AnUnknownIdentity_IsForbidden_BeforeMfaIsConsultedAsync |
Fact | Negotiate with mfa an unknown identity is forbidden before mfa is consulted. |
NegotiateWithMfa_AProviderFailure_PropagatesAsync |
Fact | Negotiate with mfa a provider failure propagates. |
NegotiateWithoutMfa_IssuesCookiesStraightAway_AndSuppliedCodesAreIgnoredAsync |
Fact | Negotiate without mfa issues cookies straight away and supplied codes are ignored. |
AuthenticationNegotiateTests
Jube.Test.Service.Authentication.AuthenticationNegotiateTests — 16 tests. Category: Service. Collection: Database.
| Test | Kind | Verifies |
|---|---|---|
GetByNegotiate_ForAKnownActiveUser_IssuesCookies_AndRecordsAType2SuccessAsync |
Fact | Get by negotiate for a known active user issues cookies and records a type2 success. |
PostByNegotiateMfa_ForAKnownActiveUser_IssuesCookies_WithoutMfaConfiguredAsync |
Fact | Post by negotiate mfa for a known active user issues cookies without mfa configured. |
UnknownWindowsIdentity_IsForbidden_AndRecordedAsFailureType1UnderThatIdentityAsync |
Fact | Unknown windows identity is forbidden and recorded as failure type1 under that identity. |
IdentityMatchingIsExact_DomainQualifiersAreNotStripped_AndCaseMattersAsync |
Theory | Identity matching is exact domain qualifiers are not stripped and case matters. |
ADomainQualifiedRegistryName_MatchesTheSameQualifiedIdentityAsync |
Theory | A domain qualified registry name matches the same qualified identity. |
DeletedInactiveOrLockedUsers_AreForbidden_WithTheMatchingFailureTypeAsync |
Theory | Deleted inactive or locked users are forbidden with the matching failure type. |
Negotiate_DoesNotCheckPasswordsExpiryOrFailureCountersAsync |
Fact | Negotiate does not check passwords expiry or failure counters. |
GetByNegotiate_AnUnauthenticatedIdentity_IsUnauthorised_ButThePostIsForbiddenAsync |
Fact | Get by negotiate an unauthenticated identity is unauthorised but the post is forbidden. |
AnAuthenticatedIdentityWithoutAName_IsUnauthorised_OnBothRoutesAsync |
Fact | An authenticated identity without a name is unauthorised on both routes. |
BothRoutes_Are404_WhenNegotiateIsNotTheActiveSchemeAsync |
Theory | Both routes are404 when negotiate is not the active scheme. |
HostileIdentities_MatchNothingAsync |
Theory | Hostile identities match nothing. |
ACancelledToken_IsSwallowedByTheGenericCatch_AndBecomesForbiddenAsync |
Fact | A cancelled token is swallowed by the generic catch and becomes forbidden. |
ControlCharactersInAnIdentity_NeverReachTheLogOrAuditLinesRawAsync |
Fact | Control characters in an identity never reach the log or audit lines raw. |
TheAuditLine_NamesTheEstablishedIdentityAsActor_AndTheOutcomeAsync |
Fact | The audit line names the established identity as actor and the outcome. |
NegotiateOutcomes_AreLogged_SuccessAtInfo_AndFailureAtErrorWithTheCauseAsync |
Fact | Negotiate outcomes are logged success at info and failure at error with the cause. |
ManyParallelNegotiateLogins_AllSucceedAndAllAreRecordedAsync |
Fact | Many parallel negotiate logins all succeed and all are recorded. |
AuthenticationPasswordLoginTests
Jube.Test.Service.Authentication.AuthenticationPasswordLoginTests — 24 tests. Category: Service. Collection: Database.
| Test | Kind | Verifies |
|---|---|---|
CorrectPassword_IssuesCookiesForTheUser_AndRecordsASuccessfulLoginAsync |
Fact | Correct password issues cookies for the user and records a successful login. |
WrongPassword_IsUnauthorised_IssuesNothing_IncrementsTheCounter_AndRecordsFailureType5Async |
Fact | Wrong password is unauthorised issues nothing increments the counter and records failure type5. |
UnknownUser_IsUnauthorised_AndRecordedAsFailureType1UnderTheAttemptedNameAsync |
Fact | Unknown user is unauthorised and recorded as failure type1 under the attempted name. |
BlankUserName_FailsValidation_TouchesNothingAsync |
Theory | Blank user name fails validation touches nothing. |
BlankPassword_FailsValidation_TouchesNothing_AndDoesNotCountAsAFailureAsync |
Theory | Blank password fails validation touches nothing and does not count as a failure. |
NullUserAndNullPassword_ReportBothValidationErrorsAsync |
Fact | Null user and null password report both validation errors. |
HostileUserName_IsJustAnUnknownUser_AndTheRealUserIsUntouchedAsync |
Theory | Hostile user name is just an unknown user and the real user is untouched. |
HostilePassword_IsJustAWrongPasswordAsync |
Theory | Hostile password is just a wrong password. |
UserNameWithANulByte_IsRejected_AndNothingIsRecordedAsync |
Fact | User name with a nul byte is rejected and nothing is recorded. |
OneMegabyteUserName_IsUnauthorised_ButLeavesNoLoginHistory_BecauseTheIndexRowLimitRejectsTheInsertAsync |
Fact | One megabyte user name is unauthorised but leaves no login history because the index row limit rejects the insert. |
Weakness_AHundredKilobyteUserName_IsPersistedInFullInTheLoginHistoryAsync |
Fact | Weakness a hundred kilobyte user name is persisted in full in the login history. |
OneMegabytePassword_IsJustAWrongPasswordAsync |
Fact | One megabyte password is just a wrong password. |
UnicodeUserNameAndPassword_AuthenticateExactlyAsync |
Theory | Unicode user name and password authenticate exactly. |
UnicodeNormalisationDifferences_DoNotMatchAsync |
Fact | Unicode normalisation differences do not match. |
UserNameMatchingIsExact_CaseAndWhitespaceSensitiveAsync |
Theory | User name matching is exact case and whitespace sensitive. |
PasswordMatchingIsExact_CaseSensitiveAsync |
Fact | Password matching is exact case sensitive. |
AuthenticatedIdentityOnTheRequest_WinsOverTheSubmittedUserNameAsync |
Fact | Authenticated identity on the request wins over the submitted user name. |
UnparsablePasswordAttemptsSetting_MakesEveryLoginFail_EvenTheCorrectOneAsync |
Theory | Unparsable password attempts setting makes every login fail even the correct one. |
AnEmptyPasswordAttemptsSetting_IsUnsetAndFallsBackToTheDefault_SoTheCorrectLoginSucceedsAsync |
Fact | An empty password attempts setting is unset and falls back to the default so the correct login succeeds. |
ThePasswordHashingKeyFromTheEnvironmentIsWhatVerifyReceivesAsync |
Fact | The password hashing key from the environment is what verify receives. |
ASpoofedBodyRemoteIp_NeverReachesTheLoginHistory_TheConnectionAddressDoesAsync |
Fact | A spoofed body remote ip never reaches the login history the connection address does. |
ASpoofedBodyRemoteIp_IsIgnoredOnFailuresToo_AndWhenTheConnectionHasNoneItIsNullAsync |
Theory | A spoofed body remote ip is ignored on failures too and when the connection has none it is null. |
ACancelledToken_AbortsBeforeAnyCredentialWorkAsync |
Fact | A cancelled token aborts before any credential work. |
EachLoginWritesExactlyOneAuditLine_WithAnonymousActor_AndNoCredentialsAsync |
Fact | Each login writes exactly one audit line with anonymous actor and no credentials. |
OAuthOrNegotiateEnabled_HidesTheUserNamePasswordRouteAsync |
Fact | O auth or negotiate enabled hides the user name password route. |
AuthenticationPasswordSchemeTests
Jube.Test.Service.Authentication.AuthenticationPasswordSchemeTests — 49 tests. Category: Service. Collection:
Database.
| Test | Kind | Verifies |
|---|---|---|
Argon2_HashesVerify_AreSalted_AndCarryTheArgon2Prefix |
Fact | Argon2 hashes verify are salted and carry the argon2 prefix. |
Argon2_Verify_IsExactOnPasswordAndKey |
Theory | Argon2 verify is exact on password and key. |
Argon2_NullKey_NeverVerifies_EvenTheRightPassword |
Fact | Argon2 null key never verifies even the right password. |
Argon2_EmptyKey_UsesTheUnkeyedForm_AndDoesNotVerifyAgainstAKeyedHash |
Fact | Argon2 empty key uses the unkeyed form and does not verify against a keyed hash. |
Argon2_HandlesUnusualPasswords_ExactlyAndFailsClosedOnNeighbours |
Theory | Argon2 handles unusual passwords exactly and fails closed on neighbours. |
Argon2_OneMegabytePassword_HashesAndVerifies |
Fact | Argon2 one megabyte password hashes and verifies. |
Argon2_ATamperedHash_FailsClosed_NeverVerifies |
Fact | Argon2 a tampered hash fails closed never verifies. |
Sha256_ProducesLowerCaseHexOfTheUtf8Bytes |
Theory | Sha256 produces lower case hex of the utf8 bytes. |
Sha256_IsDeterministic_AndUnicodeSafe |
Fact | Sha256 is deterministic and unicode safe. |
RealArgon2_EndToEndLogin_AcceptsTheRightPasswordAndRejectsTheWrongOneAsync |
Fact | Real argon2 end to end login accepts the right password and rejects the wrong one. |
RealArgon2_NewPasswordIsStoredAsAnArgon2Hash_AndVerifiesAfterwardsAsync |
Fact | Real argon2 new password is stored as an argon2 hash and verifies afterwards. |
AWrongPasswordCostsTheSameHashWorkAsARightOne_ForAKnownUserAsync |
Fact | A wrong password costs the same hash work as a right one for a known user. |
WirePasswordHash_ReflectsTheUsersFlag_AndUnknownAndDeletedUsersLookLikeWireUsersAsync |
Theory | Wire password hash reflects the users flag and unknown and deleted users look like wire users. |
WirePasswordHash_NullUserName_IsFalse_WithoutTouchingTheDatabaseAsync |
Fact | Wire password hash null user name is false without touching the database. |
Weakness_WirePasswordHash_ProbeDistinguishesWireZeroUsersFromUnknownOnesAsync |
Fact | Weakness wire password hash probe distinguishes wire zero users from unknown ones. |
WirePasswordHash_IsAlways404_WhenAnotherSchemeIsEnabled_EvenForAnAuthenticatedCallerAsync |
Theory | Wire password hash is always404 when another scheme is enabled even for an authenticated caller. |
Encryption_On_ACorrectlyEncryptedPassword_AuthenticatesAsync |
Fact | Encryption on a correctly encrypted password authenticates. |
Encryption_On_AnEncryptedWrongPassword_IsABadCredential_AndCountsAsync |
Fact | Encryption on an encrypted wrong password is a bad credential and counts. |
Encryption_On_UnicodePassword_RoundTripsAsync |
Fact | Encryption on unicode password round trips. |
Encryption_On_ThePlaintextPassword_IsNotAcceptable_AndLeavesNoTraceAsync |
Fact | Encryption on the plaintext password is not acceptable and leaves no trace. |
Encryption_On_ACiphertextForTheWrongPrivateKey_IsRejectedAsync |
Fact | Encryption on a ciphertext for the wrong private key is rejected. |
Encryption_On_TamperedOrGarbledCiphertext_NeverAuthenticatesAsync |
Theory | Encryption on tampered or garbled ciphertext never authenticates. |
Encryption_On_WhitespaceInsideTheBase64_IsIgnoredByTheDecoder_SoStillAuthenticatesAsync |
Fact | Encryption on whitespace inside the base64 is ignored by the decoder so still authenticates. |
UndecryptableCiphertext_IsACountedFailure_LocksTheAccount_AndLeavesAHistoryRowAsync |
Fact | Undecryptable ciphertext is a counted failure locks the account and leaves a history row. |
Encryption_Off_ACiphertextIsJustAWrongPasswordAsync |
Fact | Encryption off a ciphertext is just a wrong password. |
Encryption_Off_ThePlaintextAuthenticatesAsync |
Fact | Encryption off the plaintext authenticates. |
Weakness_EncryptionOnWithoutAPrivateKey_SilentlyAcceptsPlaintextPasswordsAsync |
Fact | Weakness encryption on without a private key silently accepts plaintext passwords. |
Encryption_On_TheNewPasswordIsDecryptedBeforeItIsHashedAndStoredAsync |
Fact | Encryption on the new password is decrypted before it is hashed and stored. |
Encryption_On_AnUndecryptableNewPassword_ThrowsAfterTheOldOneVerified_AndChangesNothingAsync |
Fact | Encryption on an undecryptable new password throws after the old one verified and changes nothing. |
CorrectPasswordWithANewOne_ChangesThePassword_ResetsCounters_AndExtendsExpiryBy90DaysAsync |
Fact | Correct password with a new one changes the password resets counters and extends expiry by90 days. |
AnExpiredPassword_CanBeReplaced_ByPresentingTheOldOneWithANewOneAsync |
Fact | An expired password can be replaced by presenting the old one with a new one. |
AWeakNewPassword_IsRejectedWithTheSpecificRule_AndNothingChangesAsync |
Theory | A weak new password is rejected with the specific rule and nothing changes. |
ANewPasswordLongerThan128Characters_IsRejectedAsync |
Fact | A new password longer than128 characters is rejected. |
ManyWeaknesses_AreAllReportedTogetherAsync |
Fact | Many weaknesses are all reported together. |
Weakness_WirePasswordHashUsers_HaveNoStrengthRulesApplied_BecauseTheServerOnlySeesAHashAsync |
Fact | Weakness wire password hash users have no strength rules applied because the server only sees a hash. |
ANewPasswordWithAWrongCurrentOne_ChangesNothingAsync |
Fact | A new password with a wrong current one changes nothing. |
ALockedUser_CannotChangeThePasswordThroughLoginAsync |
Fact | A locked user cannot change the password through login. |
PasswordChangeState_WithABlankPassword_IsAPasswordEmptyFailure_Type6Async |
Fact | Password change state with a blank password is a password empty failure type6. |
PasswordChangeState_WithoutANewPassword_FailsValidationAsync |
Fact | Password change state without a new password fails validation. |
ChangePassword_CorrectCurrentAndStrongNew_ReplacesTheHash_WithAnEmptySuccessAsync |
Fact | Change password correct current and strong new replaces the hash with an empty success. |
ChangePassword_WrongCurrent_IsUnauthorised_ChangesNothing_AndIsNotCountedAsync |
Fact | Change password wrong current is unauthorised changes nothing and is not counted. |
ChangePassword_MissingNewPassword_IsAStrengthFailureAsync |
Theory | Change password missing new password is a strength failure. |
ChangePassword_WeakNew_ListsTheBrokenRulesAsync |
Fact | Change password weak new lists the broken rules. |
ChangePassword_IsForbidden_WhenAnotherSchemeOwnsIdentityAsync |
Theory | Change password is forbidden when another scheme owns identity. |
ChangePassword_WithoutAnIdentityName_IsABadRequestWithTheLegacyMessageAsync |
Fact | Change password without an identity name is a bad request with the legacy message. |
ChangePassword_EmptyCurrentPassword_IsABadRequest_NotAnExceptionAsync |
Fact | Change password empty current password is a bad request not an exception. |
ChangePassword_ForAnIdentityWithNoUserRecord_IsUnauthorised_WithEquivalentHashWorkAsync |
Fact | Change password for an identity with no user record is unauthorised with equivalent hash work. |
Weakness_ChangePassword_UnlocksALockedAccount_BecauseSetPasswordClearsTheLockAsync |
Fact | Weakness change password unlocks a locked account because set password clears the lock. |
ChangePassword_EncryptedCurrentAndNew_AreDecryptedAsync |
Fact | Change password encrypted current and new are decrypted. |
AuthenticationRsaMfaLockoutTests
Jube.Test.Service.Authentication.AuthenticationRsaMfaLockoutTests — 2 tests. Category: Service. Collection: Database.
| Test | Kind | Verifies |
|---|---|---|
WrongRsaCodes_AreCounted_LockTheAccount_AndTheRightCodeIsThenRefusedAsync |
Fact | Wrong rsa codes are counted lock the account and the right code is then refused. |
TheRightRsaCode_CompletesTheLoginAndResetsTheCounterAsync |
Fact | The right rsa code completes the login and resets the counter. |
AuthenticationRsaMfaTests
Jube.Test.Service.Authentication.AuthenticationRsaMfaTests — 11 tests. Category: Service.
| Test | Kind | Verifies |
|---|---|---|
TheCorrectCode_VerifiesAsync |
Fact | The correct code verifies. |
AnyOtherCode_IsRejectedAsync |
Theory | Any other code is rejected. |
TheRequest_CarriesTheSubjectClientIdAndTheSecuridFactorAsync |
Fact | The request carries the subject client id and the securid factor. |
AWrongClientKey_IsAProviderFailure_NotAFalseAsync |
Fact | A wrong client key is a provider failure not a false. |
AnUnreachableProvider_ThrowsRatherThanAcceptingTheCodeAsync |
Fact | An unreachable provider throws rather than accepting the code. |
ACancelledToken_AbortsTheCallAsync |
Fact | A cancelled token aborts the call. |
TheRsaProviderNeverLogsTheMfaCode_OnSuccessOrRejectionAsync |
Theory | The rsa provider never logs the mfa code on success or rejection. |
TheProviderLog_NeverContainsTheClientKeyAsync |
Fact | The provider log never contains the client key. |
ProviderFailures_AreLoggedAtError_WithoutTheCodeAsync |
Fact | Provider failures are logged at error without the code. |
ARejectedProviderResponse_LeavesNoCodeOrResponseBodyInAnyLogAsync |
Fact | A rejected provider response leaves no code or response body in any log. |
Weakness_EachVerification_BuildsANewProviderAndHttpClientAsync |
Fact | Weakness each verification builds a new provider and http client. |
AuthenticationSecretsInLogsTests
Jube.Test.Service.Authentication.AuthenticationSecretsInLogsTests — 2 tests. Category: Service. Collection: Database.
| Test | Kind | Verifies |
|---|---|---|
NoSecretEverReachesAnyLogOrAuditLineAsync |
Theory | No secret ever reaches any log or audit line. |
EveryScenario_TogetherProduceAtLeastOneAuditLine_PerOperation_AndNoneCarryAPasswordAsync |
Fact | Every scenario together produce at least one audit line per operation and none carry a password. |
LoginSourceIpThrottleTests
Jube.Test.Service.Authentication.LoginSourceIpThrottleTests — 6 tests. Category: Unit.
| Test | Kind | Verifies |
|---|---|---|
SuccessfulOrUnrecordedAttemptsAreNeverThrottled |
Fact | Successful or unrecorded attempts are never throttled. |
FailedAttemptsWithinTheThresholdAreNeverExceeded |
Fact | Failed attempts within the threshold are never exceeded. |
TheFailedAttemptThatCrossesTheThresholdIsExceeded |
Fact | The failed attempt that crosses the threshold is exceeded. |
DifferentSourceIpsAreCountedIndependently |
Fact | Different source ips are counted independently. |
TheWindowResetsOnceItHasElapsed |
Fact | The window resets once it has elapsed. |
ANullOrEmptyRemoteIpIsNeverThrottled |
Fact | A null or empty remote ip is never throttled. |
OAuthPromptSelectorTests
Jube.Test.Service.Authentication.OAuthPromptSelectorTests — 3 tests. Category: Unit.
| Test | Kind | Verifies |
|---|---|---|
Resolve_AppliesTheConfiguredPromptWhenNoneIsAlreadySet |
Theory | Resolve applies the configured prompt when none is already set. |
Resolve_LeavesThePromptUnsetWhenNothingIsConfigured |
Theory | Resolve leaves the prompt unset when nothing is configured. |
Resolve_NeverOverwritesAPromptAlreadyOnTheProtocolMessage |
Fact | Resolve never overwrites a prompt already on the protocol message. |
OAuthSignInInterstitialTests
Jube.Test.Service.Authentication.OAuthSignInInterstitialTests — 4 tests. Category: Unit.
| Test | Kind | Verifies |
|---|---|---|
IsSafeTarget_AcceptsLocalPathsAndHttpUrls |
Theory | Is safe target accepts local paths and http urls. |
IsSafeTarget_RejectsOpenRedirectsSchemesAndHeaderInjection |
Theory | Is safe target rejects open redirects schemes and header injection. |
Write_SetsA200WithRefreshToTheTargetAndNoCaching |
Fact | Write sets a 200 with refresh to the target and no caching. |
ApplyNoCacheHeaders_SetsCacheControlPragmaAndExpires |
Fact | Apply no cache headers sets cache control pragma and expires. |
Role grant access
CaseWorkflowRoleGrantAccessTests
Jube.Test.Service.RoleGrantAccess.CaseWorkflowRoleGrantAccessTests — 8 tests.
| Test | Kind | Verifies |
|---|---|---|
NoGrantMeansNoAccessAsync |
Theory | No grant means no access. |
GrantGivesAccessToThatRoleOnlyAsync |
Theory | Grant gives access to that role only. |
RevokeRemovesAccessAtOnceAsync |
Theory | Revoke removes access at once. |
RegrantAfterRevokeRestoresAccessAsync |
Theory | Regrant after revoke restores access. |
EachRoleKeepsItsOwnAccessAsync |
Theory | Each role keeps its own access. |
DeletedRoleLosesAccessWithoutErrorsAsync |
Theory | Deleted role loses access without errors. |
MovingAUserToAnotherRoleMovesTheirAccessAsync |
Theory | Moving a user to another role moves their access. |
AnotherTenantsRoleNeverGrantsAsync |
Theory | Another tenants role never grants. |
EntityAnalysisModelRoleGrantAccessTests
Jube.Test.Service.RoleGrantAccess.EntityAnalysisModelRoleGrantAccessTests — 8 tests.
| Test | Kind | Verifies |
|---|---|---|
NoGrantMeansNoAccessAsync |
Fact | No grant means no access. |
GrantGivesAccessToThatRoleOnlyAsync |
Fact | Grant gives access to that role only. |
RevokeRemovesAccessAtOnceAsync |
Fact | Revoke removes access at once. |
RegrantAfterRevokeRestoresAccessAsync |
Fact | Regrant after revoke restores access. |
EachRoleKeepsItsOwnAccessAsync |
Fact | Each role keeps its own access. |
DeletedRoleLosesAccessWithoutErrorsAsync |
Fact | Deleted role loses access without errors. |
MovingAUserToAnotherRoleMovesTheirAccessAsync |
Fact | Moving a user to another role moves their access. |
AnotherTenantsRoleNeverGrantsAsync |
Fact | Another tenants role never grants. |
VisualisationRoleGrantAccessTests
Jube.Test.Service.RoleGrantAccess.VisualisationRoleGrantAccessTests — 8 tests.
| Test | Kind | Verifies |
|---|---|---|
NoGrantMeansNoAccessAsync |
Theory | No grant means no access. |
GrantGivesAccessToThatRoleOnlyAsync |
Theory | Grant gives access to that role only. |
RevokeRemovesAccessAtOnceAsync |
Theory | Revoke removes access at once. |
RegrantAfterRevokeRestoresAccessAsync |
Theory | Regrant after revoke restores access. |
EachRoleKeepsItsOwnAccessAsync |
Theory | Each role keeps its own access. |
DeletedRoleLosesAccessWithoutErrorsAsync |
Theory | Deleted role loses access without errors. |
MovingAUserToAnotherRoleMovesTheirAccessAsync |
Theory | Moving a user to another role moves their access. |
AnotherTenantsRoleNeverGrantsAsync |
Theory | Another tenants role never grants. |
Security
HtmlSanitiserHardeningTests
Jube.Test.Service.Security.HtmlSanitiserHardeningTests — 6 tests. Category: Service.
| Test | Kind | Verifies |
|---|---|---|
ImageSourcesThatAreSameOriginOrExecutableAreDropped |
Theory | Image sources that are same origin or executable are dropped. |
SafeImageSourcesAreKept |
Theory | Safe image sources are kept. |
LinksAndOrdinaryMarkupAreUnchangedAndScriptIsStillDropped |
Fact | Links and ordinary markup are unchanged and script is still dropped. |
StyleValuesCannotCarryAUrlOrAnExpressionWithParentheses |
Theory | Style values cannot carry a url or an expression with parentheses. |
HarmlessParenthesisedStyleValuesAreKept |
Fact | Harmless parenthesised style values are kept. |
HostileLongInputCostsLinearTime |
Fact | Hostile long input costs linear time. |
User login
UserLoginServiceTests
Jube.Test.Service.UserLogin.UserLoginServiceTests — 19 tests. Category: Service. Collection: Database.
| Test | Kind | Verifies |
|---|---|---|
ListReturnsMostRecentFirstAndProjectsFieldsAsync |
Fact | List returns most recent first and projects fields. |
ListDescribesOAuthFailureWithMessageAsync |
Fact | List describes o auth failure with message. |
ListClampsTakeToOneHundredThousandAsync |
Fact | List clamps take to one hundred thousand. |
ListWithoutPermissionThrowsForbiddenAsync |
Fact | List without permission throws forbidden. |
CreateWithNullUserNameThrowsNotAuthenticatedAsync |
Fact | Create with null user name throws not authenticated. |
CreateWithUnknownTenantThrowsNotAuthenticatedAsync |
Fact | Create with unknown tenant throws not authenticated. |
ListFiltersByDateRangeAsync |
Fact | List filters by date range. |
ListFiltersBySearchAsync |
Fact | List filters by search. |
ListWithZeroSamplePercentageExcludesEveryMatchingRowAsync |
Fact | List with zero sample percentage excludes every matching row. |
ListWithHundredSamplePercentageIncludesEveryMatchingRowAsync |
Fact | List with hundred sample percentage includes every matching row. |
ListIsScopedToTheCallersTenantAsync |
Fact | List is scoped to the callers tenant. |
AttemptsAgainstUnknownNamesAreVisibleToALandlordOnlyAsync |
Fact | Attempts against unknown names are visible to a landlord only. |
LandlordSeesTheLoginAttemptsOfEveryTenantAsync |
Fact | Landlord sees the login attempts of every tenant. |
ListSortsByCreatedUserAscendingAndDescendingAsync |
Fact | List sorts by created user ascending and descending. |
ListSortDirectionIsCaseInsensitiveForAscendingAsync |
Theory | List sort direction is case insensitive for ascending. |
ListWithGarbageSortDirectionFallsBackToDescendingAsync |
Fact | List with garbage sort direction falls back to descending. |
ListWithUnrecognisedSortFieldFallsBackToMostRecentFirstAsync |
Fact | List with unrecognised sort field falls back to most recent first. |
ListTotalReflectsFullFilteredCountEvenWhenTakeIsSmallerAsync |
Fact | List total reflects full filtered count even when take is smaller. |
ListStatisticsIsEmptyForThisDtoAsync |
Fact | List statistics is empty for this dto. |
User logout
UserLogoutServiceTests
Jube.Test.Service.UserLogout.UserLogoutServiceTests — 27 tests. Category: Service. Collection: Database.
| Test | Kind | Verifies |
|---|---|---|
ListReturnsMostRecentFirstAndProjectsFieldsAsync |
Fact | List returns most recent first and projects fields. |
ListDescribesEveryReasonAndOutcomeAsync |
Theory | List describes every reason and outcome. |
ListProjectsHowLongTheSessionLivedAsync |
Fact | List projects how long the session lived. |
ListLeavesTheSessionLengthEmptyWhenTheStartIsUnknownAsync |
Fact | List leaves the session length empty when the start is unknown. |
ListClampsTakeToOneHundredThousandAsync |
Fact | List clamps take to one hundred thousand. |
ListWithoutPermissionThrowsForbiddenAsync |
Fact | List without permission throws forbidden. |
CreateWithNullUserNameThrowsNotAuthenticatedAsync |
Fact | Create with null user name throws not authenticated. |
CreateWithUnknownTenantThrowsNotAuthenticatedAsync |
Fact | Create with unknown tenant throws not authenticated. |
ListFiltersByDateRangeAsync |
Fact | List filters by date range. |
ListDefaultsToTheLastHourAsync |
Fact | List defaults to the last hour. |
ListFiltersBySearchAsync |
Fact | List filters by search. |
ListSearchMatchesEveryTextColumnCaseInsensitivelyAsync |
Theory | List search matches every text column case insensitively. |
ListWithZeroSamplePercentageExcludesEveryMatchingRowAsync |
Fact | List with zero sample percentage excludes every matching row. |
ListWithHundredSamplePercentageIncludesEveryMatchingRowAsync |
Fact | List with hundred sample percentage includes every matching row. |
ListIsScopedToTheCallersTenantAsync |
Fact | List is scoped to the callers tenant. |
RowsOfNoTenantAreVisibleToALandlordOnlyAsync |
Fact | Rows of no tenant are visible to a landlord only. |
LandlordSeesTheSessionsCutInEveryTenantAsync |
Fact | Landlord sees the sessions cut in every tenant. |
ListSortsByCreatedUserAscendingAndDescendingAsync |
Fact | List sorts by created user ascending and descending. |
ListSortDirectionIsCaseInsensitiveForAscendingAsync |
Theory | List sort direction is case insensitive for ascending. |
ListWithGarbageSortDirectionFallsBackToDescendingAsync |
Fact | List with garbage sort direction falls back to descending. |
ListSortsByEveryDocumentedColumnWithoutErrorAsync |
Theory | List sorts by every documented column without error. |
ListWithUnrecognisedSortFieldFallsBackToMostRecentFirstAsync |
Fact | List with unrecognised sort field falls back to most recent first. |
ListTotalReflectsFullFilteredCountEvenWhenTakeIsSmallerAsync |
Fact | List total reflects full filtered count even when take is smaller. |
ListStatisticsIsEmptyForThisDtoAsync |
Fact | List statistics is empty for this dto. |
ListEmitsOneAuditLineAndOneSpanAndNoChangeEventAsync |
Fact | List emits one audit line and one span and no change event. |
ForbiddenListIsAuditedAsForbiddenAsync |
Fact | Forbidden list is audited as forbidden. |
ToolIsDeclaredOnTheServiceMethodAndPublishedByTheCatalogue |
Fact | Tool is declared on the service method and published by the catalogue. |
