Class AuditedAspectTest
java.lang.Object
ubic.gemma.core.util.test.BaseTest5
ubic.gemma.core.security.audit.AuditedAspectTest
Unit-level test for
AuditedAspect: the aspect intercepts the
Audited annotation, locates the first Auditable argument
and optional AuditEventPayload argument, serialises the payload to
JSON, delegates to AuditTrailService.addUpdateEventWithPayload(Auditable, Class, String, String), and
publishes a Spring AuditedEvent.
Phase A round-trip verification for AUDIT_SYSTEM_AUDIT.md.
-
Nested Class Summary
Nested ClassesModifier and TypeClassDescriptionstatic classMinimal in-memory Auditable that doesn't require Hibernate or any of the gemma-core entity machinery.static final recordA locally-defined payload record.static classStatic helper invoked fromvalueSpelexpressions viaT(...).pick(#arg). -
Constructor Summary
Constructors -
Method Summary
Modifier and TypeMethodDescriptionvoidPhase C: a brokenwhenSpEL must SKIP emission (safe default — an undecidable predicate is closer to the no-op branch than to the emission branch).voidPhase C:AuditedConditionalfires only when itswhenpredicate is true.voidPhase C: predicate can reference a method parameter by name (same resolution asmessageSpel).voidPhase C: when the predicate is false the aspect must skip emission entirely — no service call, no Spring event.voidPhase C: brokenmessageSpelon the throwing path must fall back to the literalmessage()and STILL write the audit row.voidPhase C: a method returning normally must NOT trigger@AuditedOnError(the advice is@AfterThrowing-only).voidPhase C: SpEL on the throwing path can reference both#exceptionand method parameters by name.voidPhase C (AuditedOnError): the aspect must (a) write a Failed* event via the 4-arg Throwable overload (REQUIRES_NEW in production), (b) re-throw the original exception, and (c) publish anAuditedEventfor downstream listeners.voidvoidPhase C: dual-annotated method on the failure path — only the@AuditedOnErrorpath fires; the@Audited@AfterReturningadvice does not.voidPhase C: a method may carry BOTHAuditedANDAuditedOnError— the two advice paths are disjoint.voidvoidvoid🛑 And no reason must leave the note exactly as it was.voidA supplied reason joins the server's mechanical description rather than replacing it — cab's ask was explicitly "appended to the server's own note", so the description survives.voidvoidPhase C (repeatable): a default-class declaration acts as a fallback when no more-specific declaration matches.voidPhase C (repeatable): two repeated declarations dispatch to different event types.voidPhase C (repeatable): two repeated declarations dispatch to different event types.voidPhase C (repeatable): most-specific match wins.voidPhase C (repeatable): when no declaration matches the throwable, nothing is recorded — like a Java multi-catch where the thrown type isn't in the catch list.voidsetUp()voidvoidPhase C (repeatable): a singleAuditedOnErrordeclaration with a defaultThrowable.classfilter (back-compat) still fires for everything.voidPhase C (repeatable): a singleAuditedOnErrordeclaration with a specificexceptionfilter fires when the throwable matches.voidPhase C (repeatable): a singleAuditedOnErrordeclaration with a specificexceptionfilter SKIPS when the throwable doesn't match — the original exception still propagates (Spring AOP re-throws regardless).voidPhase B-2:messageSpelmust be evaluated against the method arguments (by parameter name) and the resolved string passed through toAuditTrailService.addUpdateEventWithPayload(Auditable, Class, String, String).void@AuditedConditionalpredicate false → no emission even ifvalueSpelwould have resolved.voidBothvalueandvalueSpelnon-default: the SpEL choice wins when it resolves successfully (the literalvalueis the fallback for the broken-SpEL path).voidBrokenvalueSpel: the SpEL expression cannot evaluate.voidBothvalueat its abstract default AND emptyvalueSpel: no concrete class to write, the aspect must skip emission entirely.voidvalueSpelreturnsnull(the helper has no class forwhich == "x");value()is at its abstract default.voidvoidvalueSpelresolves toSampleRemovalReversionEventwhenwhich == "b"— different concrete class from branch A, on the same annotated method.void@AuditedConditionalalso honoursvalueSpel: when the predicate fires, the event class is resolved at runtime.
-
Constructor Details
-
AuditedAspectTest
public AuditedAspectTest()
-
-
Method Details
-
setUp
@BeforeEach public void setUp() -
simpleRemove_writesTypedEventWithLiteralMessage_andPublishesSpringEvent
@Test public void simpleRemove_writesTypedEventWithLiteralMessage_andPublishesSpringEvent() -
removeWithPayload_serialisesJsonAndIncludesTypeDiscriminator
-
noAuditableArg_logsWarnAndDoesNotCallService
@Test public void noAuditableArg_logsWarnAndDoesNotCallService() -
testMessageSpelEvaluation
@Test public void testMessageSpelEvaluation()Phase B-2:messageSpelmust be evaluated against the method arguments (by parameter name) and the resolved string passed through toAuditTrailService.addUpdateEventWithPayload(Auditable, Class, String, String). -
optionalReason_isAppendedToTheServersOwnNote
@Test public void optionalReason_isAppendedToTheServersOwnNote()A supplied reason joins the server's mechanical description rather than replacing it — cab's ask was explicitly "appended to the server's own note", so the description survives. -
noReason_leavesTheNoteUntouched
@Test public void noReason_leavesTheNoteUntouched()🛑 And no reason must leave the note exactly as it was. Every existing caller passes null, so a ternary that emitted a bare separator would rewrite the note on every audited write in Gemma. -
messageSpel_canReferenceReturnValueWithHashResult
@Test public void messageSpel_canReferenceReturnValueWithHashResult() -
brokenSpel_fallsBackToLiteralMessageAndStillWritesAuditRow
@Test public void brokenSpel_fallsBackToLiteralMessageAndStillWritesAuditRow() -
auditedConditional_firesWhenPredicateTrue
@Test public void auditedConditional_firesWhenPredicateTrue()Phase C:AuditedConditionalfires only when itswhenpredicate is true.#result > 0→ audit row written. -
auditedConditional_skipsWhenPredicateFalse
@Test public void auditedConditional_skipsWhenPredicateFalse()Phase C: when the predicate is false the aspect must skip emission entirely — no service call, no Spring event. -
auditedConditional_predicateCanReferenceArgs
@Test public void auditedConditional_predicateCanReferenceArgs()Phase C: predicate can reference a method parameter by name (same resolution asmessageSpel). -
auditedConditional_brokenWhenSpelSkipsEmission
@Test public void auditedConditional_brokenWhenSpelSkipsEmission()Phase C: a brokenwhenSpEL must SKIP emission (safe default — an undecidable predicate is closer to the no-op branch than to the emission branch). -
auditedOnError_writesEventAndRethrowsOriginal
@Test public void auditedOnError_writesEventAndRethrowsOriginal()Phase C (AuditedOnError): the aspect must (a) write a Failed* event via the 4-arg Throwable overload (REQUIRES_NEW in production), (b) re-throw the original exception, and (c) publish anAuditedEventfor downstream listeners. -
auditedOnError_messageSpelCanReferenceExceptionAndArgs
@Test public void auditedOnError_messageSpelCanReferenceExceptionAndArgs()Phase C: SpEL on the throwing path can reference both#exceptionand method parameters by name. -
auditedOnError_happyPathDoesNotEmit
@Test public void auditedOnError_happyPathDoesNotEmit()Phase C: a method returning normally must NOT trigger@AuditedOnError(the advice is@AfterThrowing-only). -
auditedOnError_brokenSpelFallsBackAndStillWritesRow
@Test public void auditedOnError_brokenSpelFallsBackAndStillWritesRow()Phase C: brokenmessageSpelon the throwing path must fall back to the literalmessage()and STILL write the audit row. Contrast with@AuditedConditional.whenwhich SKIPS on broken SpEL — losing a Failed* row would hide the failure. -
dualAnnotated_successPath_firesOnlyAuditedNotOnError
@Test public void dualAnnotated_successPath_firesOnlyAuditedNotOnError()Phase C: a method may carry BOTHAuditedANDAuditedOnError— the two advice paths are disjoint. On success, only the@Auditedpath fires. -
dualAnnotated_failurePath_firesOnlyAuditedOnError
@Test public void dualAnnotated_failurePath_firesOnlyAuditedOnError()Phase C: dual-annotated method on the failure path — only the@AuditedOnErrorpath fires; the@Audited@AfterReturningadvice does not. -
singleAnnotation_defaultFilter_firesForAnyThrowable
@Test public void singleAnnotation_defaultFilter_firesForAnyThrowable()Phase C (repeatable): a singleAuditedOnErrordeclaration with a defaultThrowable.classfilter (back-compat) still fires for everything. Covered indirectly above; the explicit check here pins back-compat down so future refactors don't regress it. -
singleAnnotation_specificFilter_firesForMatchingThrowable
@Test public void singleAnnotation_specificFilter_firesForMatchingThrowable()Phase C (repeatable): a singleAuditedOnErrordeclaration with a specificexceptionfilter fires when the throwable matches. -
singleAnnotation_specificFilter_skipsForNonMatchingThrowable
@Test public void singleAnnotation_specificFilter_skipsForNonMatchingThrowable()Phase C (repeatable): a singleAuditedOnErrordeclaration with a specificexceptionfilter SKIPS when the throwable doesn't match — the original exception still propagates (Spring AOP re-throws regardless). -
repeatable_dispatchesToCorrectEventType_iae
@Test public void repeatable_dispatchesToCorrectEventType_iae()Phase C (repeatable): two repeated declarations dispatch to different event types. IAE branch → SampleRemovalEvent. -
repeatable_dispatchesToCorrectEventType_ise
@Test public void repeatable_dispatchesToCorrectEventType_ise()Phase C (repeatable): two repeated declarations dispatch to different event types. ISE branch → FailedSampleCorrelationAnalysisEvent. -
repeatable_mostSpecificMatchWins
@Test public void repeatable_mostSpecificMatchWins()Phase C (repeatable): most-specific match wins. IAE-specific declaration beats the Throwable.class fallback. -
repeatable_defaultClassFallback_firesForUnmatchedType
@Test public void repeatable_defaultClassFallback_firesForUnmatchedType()Phase C (repeatable): a default-class declaration acts as a fallback when no more-specific declaration matches. -
repeatable_noMatch_emitsNothing
@Test public void repeatable_noMatch_emitsNothing()Phase C (repeatable): when no declaration matches the throwable, nothing is recorded — like a Java multi-catch where the thrown type isn't in the catch list. The original exception still propagates. -
valueSpel_resolvesToBranchA
@Test public void valueSpel_resolvesToBranchA()valueSpelresolves toSampleRemovalEventwhenwhich == "a". The aspect must write the row with the SpEL-chosen class. -
valueSpel_resolvesToBranchB
@Test public void valueSpel_resolvesToBranchB()valueSpelresolves toSampleRemovalReversionEventwhenwhich == "b"— different concrete class from branch A, on the same annotated method. Demonstrates true runtime dispatch. -
valueSpel_nullResultSkipsEmission
@Test public void valueSpel_nullResultSkipsEmission()valueSpelreturnsnull(the helper has no class forwhich == "x");value()is at its abstract default. The aspect must SKIP emission — no fallback to the abstract base class. -
valueSpel_brokenExpressionFallsBackToLiteralValue
@Test public void valueSpel_brokenExpressionFallsBackToLiteralValue()BrokenvalueSpel: the SpEL expression cannot evaluate. The aspect must log ERROR and FALL BACK to the literalvalue(). Sincevalue()here is a concrete class, emission proceeds. -
valueSpel_bothSetSpelChoiceWins
@Test public void valueSpel_bothSetSpelChoiceWins()BothvalueandvalueSpelnon-default: the SpEL choice wins when it resolves successfully (the literalvalueis the fallback for the broken-SpEL path). -
valueSpel_noEventTypeResolvedSkips
@Test public void valueSpel_noEventTypeResolvedSkips()Bothvalueat its abstract default AND emptyvalueSpel: no concrete class to write, the aspect must skip emission entirely. -
valueSpel_worksOnAuditedConditional
@Test public void valueSpel_worksOnAuditedConditional()@AuditedConditionalalso honoursvalueSpel: when the predicate fires, the event class is resolved at runtime. -
valueSpel_auditedConditionalSkipsWhenPredicateFalse
@Test public void valueSpel_auditedConditionalSkipsWhenPredicateFalse()@AuditedConditionalpredicate false → no emission even ifvalueSpelwould have resolved. The when-gate runs first.
-