diff --git a/src/main/java/com/clearfolio/viewer/service/DefaultDocumentConversionService.java b/src/main/java/com/clearfolio/viewer/service/DefaultDocumentConversionService.java index 3ee96b9e8..8bfb1488c 100644 --- a/src/main/java/com/clearfolio/viewer/service/DefaultDocumentConversionService.java +++ b/src/main/java/com/clearfolio/viewer/service/DefaultDocumentConversionService.java @@ -176,19 +176,19 @@ public UUID submit(MultipartFile file, PolicyOverrideRequest overrideRequest) { */ @Override public UUID submit(MultipartFile file, PolicyOverrideRequest overrideRequest, TenantContext tenantContext) { + if (tenantContext == null) { + throw new IllegalArgumentException("tenant context is required"); + } PolicyOverrideRequest effectiveOverride = overrideRequest == null ? PolicyOverrideRequest.none() : overrideRequest; - TenantContext effectiveTenant = tenantContext == null - ? new TenantContext(TenantContext.DEMO_TENANT_ID, TenantContext.DEMO_SUBJECT_ID, java.util.Set.of()) - : tenantContext; validationService.validateOrThrow(file, effectiveOverride); String contentHash = contentHash(file); ConversionJob job = new ConversionJob( UUID.randomUUID(), - effectiveTenant.tenantId(), - effectiveTenant.subjectId(), + tenantContext.tenantId(), + tenantContext.subjectId(), sanitizeFilename(file.getOriginalFilename()), file.getContentType(), contentHash, diff --git a/src/test/java/com/clearfolio/viewer/service/DefaultDocumentConversionServiceTenantAuthorityTest.java b/src/test/java/com/clearfolio/viewer/service/DefaultDocumentConversionServiceTenantAuthorityTest.java new file mode 100644 index 000000000..2b5151739 --- /dev/null +++ b/src/test/java/com/clearfolio/viewer/service/DefaultDocumentConversionServiceTenantAuthorityTest.java @@ -0,0 +1,49 @@ +package com.clearfolio.viewer.service; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verifyNoInteractions; + +import org.junit.jupiter.api.Test; +import org.springframework.mock.web.MockMultipartFile; + +import com.clearfolio.viewer.artifact.ArtifactStore; +import com.clearfolio.viewer.config.ConversionProperties; +import com.clearfolio.viewer.repository.ConversionJobRepository; + +/** + * Verifies that the production tenant-aware submission boundary never invents + * demo authority when an authenticated tenant context is absent. + */ +class DefaultDocumentConversionServiceTenantAuthorityTest { + + @Test + void tenantAwareSubmitRejectsMissingAuthorityBeforeAnyDocumentOrJobWork() { + ConversionJobRepository repository = mock(ConversionJobRepository.class); + DocumentValidationService validationService = mock(DocumentValidationService.class); + ConversionWorker worker = mock(ConversionWorker.class); + ArtifactStore artifactStore = mock(ArtifactStore.class); + DefaultDocumentConversionService service = new DefaultDocumentConversionService( + repository, + validationService, + worker, + artifactStore, + new ConversionProperties() + ); + MockMultipartFile file = new MockMultipartFile( + "file", + "contract.docx", + "application/octet-stream", + "sensitive-document".getBytes(java.nio.charset.StandardCharsets.UTF_8) + ); + + IllegalArgumentException exception = assertThrows( + IllegalArgumentException.class, + () -> service.submit(file, PolicyOverrideRequest.none(), null) + ); + + assertEquals("tenant context is required", exception.getMessage()); + verifyNoInteractions(validationService, repository, worker, artifactStore); + } +} diff --git a/src/test/java/com/clearfolio/viewer/service/DefaultDocumentConversionServiceTest.java b/src/test/java/com/clearfolio/viewer/service/DefaultDocumentConversionServiceTest.java index 8d18efa00..57a313673 100644 --- a/src/test/java/com/clearfolio/viewer/service/DefaultDocumentConversionServiceTest.java +++ b/src/test/java/com/clearfolio/viewer/service/DefaultDocumentConversionServiceTest.java @@ -260,7 +260,7 @@ void submitStoresTenantAndSubjectMetadataOnJob() { } @Test - void submitWithNullTenantContextFallsBackToDemoOwnership() { + void submitWithNullTenantContextFailsClosed() { ConversionJobRepository repository = new InMemoryConversionJobRepository(); RecordingConversionWorker worker = new RecordingConversionWorker(); DocumentConversionService service = new DefaultDocumentConversionService( @@ -277,11 +277,14 @@ void submitWithNullTenantContextFallsBackToDemoOwnership() { "hello-viewer".getBytes() ); - UUID jobId = service.submit(file, PolicyOverrideRequest.none(), null); + IllegalArgumentException error = assertThrows( + IllegalArgumentException.class, + () -> service.submit(file, PolicyOverrideRequest.none(), null) + ); - ConversionJob job = repository.findById(jobId).orElseThrow(); - assertEquals(TenantContext.DEMO_TENANT_ID, job.getTenantId()); - assertEquals(TenantContext.DEMO_SUBJECT_ID, job.getSubjectId()); + assertEquals("tenant context is required", error.getMessage()); + assertTrue(repository.findAll().isEmpty()); + assertEquals(0, worker.enqueuedCount()); } @Test