From 6b17067fe89ecc3614f14e196abcb76e12f4e309 Mon Sep 17 00:00:00 2001 From: Shawn Jackson Date: Fri, 1 May 2026 19:01:24 -0700 Subject: [PATCH] RE1-T115 S3 fixes --- .../Web/Tts/S3StorageServiceTests.cs | 35 +++++++++++++++++++ .../Services/S3StorageService.cs | 7 ++++ 2 files changed, 42 insertions(+) diff --git a/Tests/Resgrid.Tests/Web/Tts/S3StorageServiceTests.cs b/Tests/Resgrid.Tests/Web/Tts/S3StorageServiceTests.cs index 4bb6c000e..3aa45a2f4 100644 --- a/Tests/Resgrid.Tests/Web/Tts/S3StorageServiceTests.cs +++ b/Tests/Resgrid.Tests/Web/Tts/S3StorageServiceTests.cs @@ -79,6 +79,41 @@ public async Task upload_async_should_treat_format_exception_as_success_when_obj s3Client.Verify(x => x.GetPreSignedURL(It.IsAny()), Times.Never); } + [Test] + public async Task upload_async_should_fall_back_to_presigned_put_when_metadata_response_is_malformed() + { + var s3Client = new Mock(MockBehavior.Strict); + s3Client + .Setup(x => x.PutObjectAsync(It.IsAny(), It.IsAny())) + .ThrowsAsync(new FormatException("bad expiration header")); + s3Client + .Setup(x => x.GetObjectMetadataAsync(It.IsAny(), It.IsAny())) + .ThrowsAsync(new FormatException("bad metadata expiration header")); + s3Client + .Setup(x => x.GetPreSignedURL(It.IsAny())) + .Returns("https://upload.example.com/tts/audio.wav?signature=metadata-format"); + + var handler = new RecordingHttpMessageHandler(async (request, cancellationToken) => + { + var body = await request.Content!.ReadAsByteArrayAsync(cancellationToken); + body.Should().Equal(2, 4, 6, 8); + request.Method.Should().Be(HttpMethod.Put); + request.RequestUri.Should().Be(new Uri("https://upload.example.com/tts/audio.wav?signature=metadata-format")); + request.Content!.Headers.ContentType!.MediaType.Should().Be("audio/wav"); + + return new HttpResponseMessage(HttpStatusCode.OK); + }); + var service = CreateService(s3Client.Object, handler); + + await using var content = new MemoryStream(new byte[] { 2, 4, 6, 8 }, writable: false); + + await service.UploadAsync("tts/audio.wav", content, "audio/wav", CancellationToken.None); + + handler.Requests.Should().HaveCount(1); + s3Client.Verify(x => x.GetObjectMetadataAsync(It.IsAny(), It.IsAny()), Times.Once); + s3Client.Verify(x => x.GetPreSignedURL(It.IsAny()), Times.Once); + } + [Test] public async Task upload_async_should_fall_back_to_presigned_put_when_metadata_check_times_out() { diff --git a/Web/Resgrid.Web.Tts/Services/S3StorageService.cs b/Web/Resgrid.Web.Tts/Services/S3StorageService.cs index ffde6c404..b5c9424ce 100644 --- a/Web/Resgrid.Web.Tts/Services/S3StorageService.cs +++ b/Web/Resgrid.Web.Tts/Services/S3StorageService.cs @@ -126,6 +126,13 @@ private async Task WasUploadPersistedAsync(string objectKey, CancellationT "Unable to verify whether {ObjectKey} exists after the PUT response parsing failure. Falling back to a presigned PUT upload.", objectKey); } + catch (FormatException ex) + { + _logger.LogWarning( + ex, + "Unable to verify whether {ObjectKey} exists after the PUT response parsing failure because the metadata response could not be parsed. Falling back to a presigned PUT upload.", + objectKey); + } catch (HttpRequestException ex) { _logger.LogWarning(