@@ -186,6 +186,146 @@ class TombstoneIntegrationTest : ApplicationExitIntegrationTestBase<TombstoneHin
186186 }
187187 }
188188
189+ @Test
190+ fun `when native event has no message, tombstone message is applied` () {
191+ val integration =
192+ fixture.getSut(tmpDir, lastReportedTimestamp = oldTimestamp) { options ->
193+ val outboxDir = File (options.outboxPath!! )
194+ outboxDir.mkdirs()
195+ createNativeEnvelope(outboxDir, newTimestamp, messageJson = null )
196+ }
197+
198+ fixture.addAppExitInfo(timestamp = newTimestamp)
199+ integration.register(fixture.scopes, fixture.options)
200+
201+ verify(fixture.scopes)
202+ .captureEvent(
203+ check<SentryEvent > { event ->
204+ // Tombstone message should be applied
205+ assertNotNull(event.message)
206+ assertNotNull(event.message!! .formatted)
207+ // The message contains the signal info from the tombstone
208+ assertTrue(event.message!! .formatted!! .contains(" Fatal signal" ))
209+ },
210+ any<Hint >(),
211+ )
212+ }
213+
214+ @Test
215+ fun `when native event has message with null template, tombstone message is applied` () {
216+ val integration =
217+ fixture.getSut(tmpDir, lastReportedTimestamp = oldTimestamp) { options ->
218+ val outboxDir = File (options.outboxPath!! )
219+ outboxDir.mkdirs()
220+ createNativeEnvelope(
221+ outboxDir,
222+ newTimestamp,
223+ messageJson = """ {"formatted":"some formatted text"}""" ,
224+ )
225+ }
226+
227+ fixture.addAppExitInfo(timestamp = newTimestamp)
228+ integration.register(fixture.scopes, fixture.options)
229+
230+ verify(fixture.scopes)
231+ .captureEvent(
232+ check<SentryEvent > { event ->
233+ // Tombstone message should be applied
234+ assertNotNull(event.message)
235+ assertNotNull(event.message!! .formatted)
236+ assertTrue(event.message!! .formatted!! .contains(" Fatal signal" ))
237+ },
238+ any<Hint >(),
239+ )
240+ }
241+
242+ @Test
243+ fun `when native event has message with empty template, tombstone message is applied` () {
244+ val integration =
245+ fixture.getSut(tmpDir, lastReportedTimestamp = oldTimestamp) { options ->
246+ val outboxDir = File (options.outboxPath!! )
247+ outboxDir.mkdirs()
248+ createNativeEnvelope(
249+ outboxDir,
250+ newTimestamp,
251+ messageJson = """ {"message":"","formatted":"some formatted text"}""" ,
252+ )
253+ }
254+
255+ fixture.addAppExitInfo(timestamp = newTimestamp)
256+ integration.register(fixture.scopes, fixture.options)
257+
258+ verify(fixture.scopes)
259+ .captureEvent(
260+ check<SentryEvent > { event ->
261+ // Tombstone message should be applied
262+ assertNotNull(event.message)
263+ assertNotNull(event.message!! .formatted)
264+ assertTrue(event.message!! .formatted!! .contains(" Fatal signal" ))
265+ },
266+ any<Hint >(),
267+ )
268+ }
269+
270+ @Test
271+ fun `when native event has message with content, native message is preserved` () {
272+ val integration =
273+ fixture.getSut(tmpDir, lastReportedTimestamp = oldTimestamp) { options ->
274+ val outboxDir = File (options.outboxPath!! )
275+ outboxDir.mkdirs()
276+ createNativeEnvelope(
277+ outboxDir,
278+ newTimestamp,
279+ messageJson =
280+ """ {"message":"Native SDK crash message","formatted":"The crash happened at 0xDEADBEEF"}""" ,
281+ )
282+ }
283+
284+ fixture.addAppExitInfo(timestamp = newTimestamp)
285+ integration.register(fixture.scopes, fixture.options)
286+
287+ verify(fixture.scopes)
288+ .captureEvent(
289+ check<SentryEvent > { event ->
290+ // Native SDK message should be preserved
291+ assertNotNull(event.message)
292+ assertEquals(" Native SDK crash message" , event.message!! .message)
293+ assertEquals(" The crash happened at 0xDEADBEEF" , event.message!! .formatted)
294+ },
295+ any<Hint >(),
296+ )
297+ }
298+
299+ /* *
300+ * Creates a native envelope file with an optional message field.
301+ *
302+ * @param messageJson The JSON for the message field (e.g.,
303+ * `{"message":"text","formatted":"text"}`), or null to omit the message field entirely.
304+ */
305+ private fun createNativeEnvelope (
306+ outboxDir : File ,
307+ timestamp : Long ,
308+ messageJson : String? = null,
309+ fileName : String = "native-envelope.envelope",
310+ ): File {
311+ val isoTimestamp = DateUtils .getTimestamp(DateUtils .getDateTime(timestamp))
312+ val messageField = if (messageJson != null ) """ ,"message":$messageJson """ else " "
313+
314+ val eventJson =
315+ """ {"event_id":"9ec79c33ec9942ab8353589fcb2e04dc","timestamp":"$isoTimestamp ","platform":"native","level":"fatal"$messageField }"""
316+ val eventJsonSize = eventJson.toByteArray(Charsets .UTF_8 ).size
317+
318+ val envelopeContent =
319+ """
320+ {"event_id":"9ec79c33ec9942ab8353589fcb2e04dc"}
321+ {"type":"event","length":$eventJsonSize ,"content_type":"application/json"}
322+ $eventJson
323+ """
324+ .trimIndent()
325+
326+ return File (outboxDir, fileName).apply { writeText(envelopeContent) }
327+ }
328+
189329 private fun createNativeEnvelopeWithAttachment (outboxDir : File , timestamp : Long ): File {
190330 val isoTimestamp = DateUtils .getTimestamp(DateUtils .getDateTime(timestamp))
191331
0 commit comments