diff --git a/src/main/java/com/clearfolio/viewer/repository/InMemoryConversionJobRepository.java b/src/main/java/com/clearfolio/viewer/repository/InMemoryConversionJobRepository.java index 74da3832..8b074888 100644 --- a/src/main/java/com/clearfolio/viewer/repository/InMemoryConversionJobRepository.java +++ b/src/main/java/com/clearfolio/viewer/repository/InMemoryConversionJobRepository.java @@ -238,10 +238,17 @@ public List findLifecycleEventsByJobId(UUID jobId) /** * Returns lifecycle events for a tenant. * + *

Missing or blank scoped tenant identifiers fail closed and never infer + * the explicit legacy demo tenant.

+ * * @param tenantId tenant identifier - * @return append-only lifecycle events for the tenant + * @return append-only lifecycle events for the tenant, or an empty list when + * scoped tenant context is absent */ public List findLifecycleEventsByTenantId(String tenantId) { + if (tenantId == null || tenantId.isBlank()) { + return List.of(); + } String normalizedTenantId = normalizeTenantId(tenantId); return lifecycleEvents.stream() .filter(event -> event.tenantId().equals(normalizedTenantId)) @@ -379,7 +386,7 @@ private String contentKey(String tenantId, String contentHash) { } private String normalizeTenantId(String tenantId) { - return tenantId == null || tenantId.isBlank() ? "buyer-demo" : tenantId.strip(); + return Objects.requireNonNull(tenantId, "tenantId").strip(); } private boolean matchesContentIndex(ConversionJob job, String expectedContentKey) { diff --git a/src/test/java/com/clearfolio/viewer/repository/InMemoryConversionJobRepositoryTenantLifecycleIsolationTest.java b/src/test/java/com/clearfolio/viewer/repository/InMemoryConversionJobRepositoryTenantLifecycleIsolationTest.java new file mode 100644 index 00000000..7a0c9828 --- /dev/null +++ b/src/test/java/com/clearfolio/viewer/repository/InMemoryConversionJobRepositoryTenantLifecycleIsolationTest.java @@ -0,0 +1,37 @@ +package com.clearfolio.viewer.repository; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.UUID; + +import org.junit.jupiter.api.Test; + +import com.clearfolio.viewer.model.ConversionJob; + +/** + * Verifies that lifecycle-event queries never infer the demo tenant from a + * missing scoped tenant identifier. + */ +class InMemoryConversionJobRepositoryTenantLifecycleIsolationTest { + + @Test + void nullAndBlankTenantQueriesCannotReadDemoLifecycleEvents() { + InMemoryConversionJobRepository repository = new InMemoryConversionJobRepository(); + ConversionJob demoJob = new ConversionJob( + UUID.fromString("11111111-2222-3333-4444-555555555555"), + "buyer-demo", + "subject-demo", + "report.pdf", + "application/pdf", + "tenant-lifecycle-isolation-hash", + 42L, + 3 + ); + repository.findOrStoreByContentHash(demoJob); + + assertEquals(1, repository.findLifecycleEventsByTenantId("buyer-demo").size()); + assertTrue(repository.findLifecycleEventsByTenantId(null).isEmpty()); + assertTrue(repository.findLifecycleEventsByTenantId(" ").isEmpty()); + } +}