Class AuditedAspectTest

java.lang.Object
ubic.gemma.core.util.test.BaseTest5
ubic.gemma.core.security.audit.AuditedAspectTest

@ContextConfiguration public class AuditedAspectTest extends BaseTest5
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.

  • Constructor Details

    • AuditedAspectTest

      public AuditedAspectTest()
  • Method Details

    • setUp

      @BeforeEach public void setUp()
    • simpleRemove_writesTypedEventWithLiteralMessage_andPublishesSpringEvent

      @Test public void simpleRemove_writesTypedEventWithLiteralMessage_andPublishesSpringEvent()
    • removeWithPayload_serialisesJsonAndIncludesTypeDiscriminator

      @Test public void removeWithPayload_serialisesJsonAndIncludesTypeDiscriminator() throws Exception
      Throws:
      Exception
    • noAuditableArg_logsWarnAndDoesNotCallService

      @Test public void noAuditableArg_logsWarnAndDoesNotCallService()
    • testMessageSpelEvaluation

      @Test public void testMessageSpelEvaluation()
      Phase B-2: messageSpel must be evaluated against the method arguments (by parameter name) and the resolved string passed through to AuditTrailService.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: AuditedConditional fires only when its when predicate 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 as messageSpel).
    • auditedConditional_brokenWhenSpelSkipsEmission

      @Test public void auditedConditional_brokenWhenSpelSkipsEmission()
      Phase C: a broken when SpEL 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 an AuditedEvent for downstream listeners.
    • auditedOnError_messageSpelCanReferenceExceptionAndArgs

      @Test public void auditedOnError_messageSpelCanReferenceExceptionAndArgs()
      Phase C: SpEL on the throwing path can reference both #exception and 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: broken messageSpel on the throwing path must fall back to the literal message() and STILL write the audit row. Contrast with @AuditedConditional.when which 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 BOTH Audited AND AuditedOnError — the two advice paths are disjoint. On success, only the @Audited path fires.
    • dualAnnotated_failurePath_firesOnlyAuditedOnError

      @Test public void dualAnnotated_failurePath_firesOnlyAuditedOnError()
      Phase C: dual-annotated method on the failure path — only the @AuditedOnError path fires; the @Audited @AfterReturning advice does not.
    • singleAnnotation_defaultFilter_firesForAnyThrowable

      @Test public void singleAnnotation_defaultFilter_firesForAnyThrowable()
      Phase C (repeatable): a single AuditedOnError declaration with a default Throwable.class filter (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 single AuditedOnError declaration with a specific exception filter fires when the throwable matches.
    • singleAnnotation_specificFilter_skipsForNonMatchingThrowable

      @Test public void singleAnnotation_specificFilter_skipsForNonMatchingThrowable()
      Phase C (repeatable): a single AuditedOnError declaration with a specific exception filter 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()
      valueSpel resolves to SampleRemovalEvent when which == "a". The aspect must write the row with the SpEL-chosen class.
    • valueSpel_resolvesToBranchB

      @Test public void valueSpel_resolvesToBranchB()
      valueSpel resolves to SampleRemovalReversionEvent when which == "b" — different concrete class from branch A, on the same annotated method. Demonstrates true runtime dispatch.
    • valueSpel_nullResultSkipsEmission

      @Test public void valueSpel_nullResultSkipsEmission()
      valueSpel returns null (the helper has no class for which == "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()
      Broken valueSpel: the SpEL expression cannot evaluate. The aspect must log ERROR and FALL BACK to the literal value(). Since value() here is a concrete class, emission proceeds.
    • valueSpel_bothSetSpelChoiceWins

      @Test public void valueSpel_bothSetSpelChoiceWins()
      Both value and valueSpel non-default: the SpEL choice wins when it resolves successfully (the literal value is the fallback for the broken-SpEL path).
    • valueSpel_noEventTypeResolvedSkips

      @Test public void valueSpel_noEventTypeResolvedSkips()
      Both value at its abstract default AND empty valueSpel: no concrete class to write, the aspect must skip emission entirely.
    • valueSpel_worksOnAuditedConditional

      @Test public void valueSpel_worksOnAuditedConditional()
      @AuditedConditional also honours valueSpel: when the predicate fires, the event class is resolved at runtime.
    • valueSpel_auditedConditionalSkipsWhenPredicateFalse

      @Test public void valueSpel_auditedConditionalSkipsWhenPredicateFalse()
      @AuditedConditional predicate false → no emission even if valueSpel would have resolved. The when-gate runs first.