diff --git a/test/Sentry.Unity.Tests/ScreenshotEventProcessorTests.cs b/test/Sentry.Unity.Tests/ScreenshotEventProcessorTests.cs index 81891bc47..de781814f 100644 --- a/test/Sentry.Unity.Tests/ScreenshotEventProcessorTests.cs +++ b/test/Sentry.Unity.Tests/ScreenshotEventProcessorTests.cs @@ -1,6 +1,7 @@ using System; using System.Collections; using System.IO; +using System.Text.RegularExpressions; using NUnit.Framework; using Sentry.Unity.Tests.Stubs; using UnityEngine; @@ -10,6 +11,26 @@ namespace Sentry.Unity.Tests; public class ScreenshotEventProcessorTests { + /// + /// Subclass that mocks screenshot capture and WaitForEndOfFrame but uses the REAL + /// CaptureAttachment implementation (via Hub.CaptureAttachment), allowing us to verify + /// that the attachment envelope actually reaches the HTTP transport. + /// + private class RealCaptureScreenshotEventProcessor : ScreenshotEventProcessor + { + public RealCaptureScreenshotEventProcessor(SentryUnityOptions options, ISentryMonoBehaviour sentryMonoBehaviour) + : base(options, sentryMonoBehaviour) { } + + internal override Texture2D CreateNewScreenshotTexture2D(SentryUnityOptions options) + => new Texture2D(1, 1); + + internal override YieldInstruction WaitForEndOfFrame() + => new YieldInstruction(); + + // CaptureAttachment is intentionally NOT overridden — the base implementation + // calls Hub.CaptureAttachment which sends a standalone attachment envelope. + } + private class TestScreenshotEventProcessor : ScreenshotEventProcessor { public Func CreateScreenshotFunc { get; set; } @@ -348,6 +369,100 @@ public IEnumerator Process_BeforeCaptureScreenshotCallbackReturnsTrue_CapturesSc Assert.AreEqual(1, screenshotCaptureCallCount); } + [UnityTest] + public IEnumerator Process_EventCapturedSuccessfully_ScreenshotAttachmentIsSent() + { + // Positive control: when the event IS captured, the screenshot coroutine should send + // the attachment. This validates the test infrastructure so the negative test below + // is meaningful — if this test passes but the next one doesn't, the WasCaptured flag + // is doing its job. + + var httpHandler = new TestHttpClientHandler("ScreenshotSuccessTest"); + var sentryMonoBehaviour = GetTestMonoBehaviour(); + + var options = new SentryUnityOptions(application: new TestApplication()) + { + Dsn = SentryTests.TestDsn, + CreateHttpMessageHandler = () => httpHandler + }; + + // Register test screenshot processor as an event processor — it will be called + // during DoSendEvent → ProcessEvent, just like the real ScreenshotEventProcessor. + options.AddEventProcessor(new RealCaptureScreenshotEventProcessor(options, sentryMonoBehaviour)); + + SentrySdk.Init(options); + + try + { + // Event goes through the full DoSendEvent pipeline and is captured successfully. + // DoSendEvent sets @event.WasCaptured = true after CaptureEnvelope succeeds. + var capturedId = SentrySdk.CaptureMessage("test message"); + Assert.AreNotEqual(SentryId.Empty, capturedId, "Sanity check: event should be captured"); + + // Wait for the screenshot coroutine to complete + yield return null; + yield return null; + + // Screenshot envelope should reach the transport + var screenshotRequest = httpHandler.GetEvent("screenshot.jpg", TimeSpan.FromSeconds(2)); + Assert.IsNotEmpty(screenshotRequest, + "Screenshot attachment should be sent when the event is captured successfully"); + } + finally + { + SentrySdk.Close(); + } + } + + [UnityTest] + public IEnumerator Process_EventDroppedByBeforeSend_ScreenshotAttachmentIsNotSent() + { + // Full pipeline test: the event goes through DoSendEvent where before_send drops it. + // The screenshot coroutine (queued during ProcessEvent, before the drop decision) + // must check WasCaptured and skip — no orphaned attachment envelope. + + var httpHandler = new TestHttpClientHandler("ScreenshotBeforeSendTest"); + var sentryMonoBehaviour = GetTestMonoBehaviour(); + + var options = new SentryUnityOptions(application: new TestApplication()) + { + Dsn = SentryTests.TestDsn, + CreateHttpMessageHandler = () => httpHandler + }; + + // Register test screenshot processor — called during DoSendEvent → ProcessEvent + options.AddEventProcessor(new RealCaptureScreenshotEventProcessor(options, sentryMonoBehaviour)); + + // Drop all events via before_send + options.SetBeforeSend((_, _) => null); + + SentrySdk.Init(options); + + try + { + // CaptureMessage goes through the full DoSendEvent pipeline: + // ProcessEvent → screenshot processor queues coroutine with @event in closure + // DoBeforeSend → returns null → event dropped, WasCaptured stays false + var capturedId = SentrySdk.CaptureMessage("test message"); + Assert.AreEqual(SentryId.Empty, capturedId, "Sanity check: before_send should drop events"); + + // Wait for the screenshot coroutine to complete + yield return null; + yield return null; + + // No screenshot envelope should reach the transport. + // GetEvent logs Debug.LogError on timeout — tell the test runner this is expected. + LogAssert.Expect(LogType.Error, new Regex("timed out")); + var screenshotRequest = httpHandler.GetEvent("screenshot.jpg", TimeSpan.FromSeconds(2)); + Assert.IsEmpty(screenshotRequest, + "Screenshot attachment should not be sent when before_send drops the event"); + } + finally + { + SentrySdk.Close(); + } + } + private static TestSentryMonoBehaviour GetTestMonoBehaviour() { var gameObject = new GameObject("ScreenshotProcessorTest");