Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions sentry/api/sentry.api
Original file line number Diff line number Diff line change
Expand Up @@ -584,6 +584,8 @@ public final class io/sentry/HubAdapter : io/sentry/IHub {
public fun captureEvent (Lio/sentry/SentryEvent;Lio/sentry/Hint;Lio/sentry/ScopeCallback;)Lio/sentry/protocol/SentryId;
public fun captureException (Ljava/lang/Throwable;Lio/sentry/Hint;)Lio/sentry/protocol/SentryId;
public fun captureException (Ljava/lang/Throwable;Lio/sentry/Hint;Lio/sentry/ScopeCallback;)Lio/sentry/protocol/SentryId;
public fun captureFeedback (Lio/sentry/protocol/Feedback;)Lio/sentry/protocol/SentryId;
public fun captureFeedback (Lio/sentry/protocol/Feedback;Lio/sentry/Hint;)Lio/sentry/protocol/SentryId;
public fun captureFeedback (Lio/sentry/protocol/Feedback;Lio/sentry/Hint;Lio/sentry/ScopeCallback;)Lio/sentry/protocol/SentryId;
public fun captureMessage (Ljava/lang/String;Lio/sentry/SentryLevel;)Lio/sentry/protocol/SentryId;
public fun captureMessage (Ljava/lang/String;Lio/sentry/SentryLevel;Lio/sentry/ScopeCallback;)Lio/sentry/protocol/SentryId;
Expand Down Expand Up @@ -2231,6 +2233,8 @@ public final class io/sentry/ScopesAdapter : io/sentry/IScopes {
public fun captureEvent (Lio/sentry/SentryEvent;Lio/sentry/Hint;Lio/sentry/ScopeCallback;)Lio/sentry/protocol/SentryId;
public fun captureException (Ljava/lang/Throwable;Lio/sentry/Hint;)Lio/sentry/protocol/SentryId;
public fun captureException (Ljava/lang/Throwable;Lio/sentry/Hint;Lio/sentry/ScopeCallback;)Lio/sentry/protocol/SentryId;
public fun captureFeedback (Lio/sentry/protocol/Feedback;)Lio/sentry/protocol/SentryId;
public fun captureFeedback (Lio/sentry/protocol/Feedback;Lio/sentry/Hint;)Lio/sentry/protocol/SentryId;
public fun captureFeedback (Lio/sentry/protocol/Feedback;Lio/sentry/Hint;Lio/sentry/ScopeCallback;)Lio/sentry/protocol/SentryId;
public fun captureMessage (Ljava/lang/String;Lio/sentry/SentryLevel;)Lio/sentry/protocol/SentryId;
public fun captureMessage (Ljava/lang/String;Lio/sentry/SentryLevel;Lio/sentry/ScopeCallback;)Lio/sentry/protocol/SentryId;
Expand Down Expand Up @@ -4747,13 +4751,15 @@ public final class io/sentry/protocol/Feedback : io/sentry/JsonSerializable, io/
public static final field TYPE Ljava/lang/String;
public fun <init> (Lio/sentry/protocol/Feedback;)V
public fun <init> (Ljava/lang/String;)V
public fun equals (Ljava/lang/Object;)Z
public fun getAssociatedEventId ()Lio/sentry/protocol/SentryId;
public fun getContactEmail ()Ljava/lang/String;
public fun getMessage ()Ljava/lang/String;
public fun getName ()Ljava/lang/String;
public fun getReplayId ()Lio/sentry/protocol/SentryId;
public fun getUnknown ()Ljava/util/Map;
public fun getUrl ()Ljava/lang/String;
public fun hashCode ()I
public fun serialize (Lio/sentry/ObjectWriter;Lio/sentry/ILogger;)V
public fun setAssociatedEventId (Lio/sentry/protocol/SentryId;)V
public fun setContactEmail (Ljava/lang/String;)V
Expand Down
10 changes: 10 additions & 0 deletions sentry/src/main/java/io/sentry/HubAdapter.java
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,16 @@ public boolean isEnabled() {
return Sentry.captureMessage(message, level, callback);
}

@Override
public @NotNull SentryId captureFeedback(@NotNull Feedback feedback) {
return Sentry.captureFeedback(feedback);
}

@Override
public @NotNull SentryId captureFeedback(@NotNull Feedback feedback, @Nullable Hint hint) {
return Sentry.captureFeedback(feedback, hint);
}

@Override
public @NotNull SentryId captureFeedback(
@NotNull Feedback feedback, @Nullable Hint hint, @Nullable ScopeCallback callback) {
Expand Down
10 changes: 10 additions & 0 deletions sentry/src/main/java/io/sentry/ScopesAdapter.java
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,16 @@ public boolean isEnabled() {
return Sentry.captureMessage(message, level, callback);
}

@Override
public @NotNull SentryId captureFeedback(@NotNull Feedback feedback) {
return Sentry.captureFeedback(feedback);
}

@Override
public @NotNull SentryId captureFeedback(@NotNull Feedback feedback, @Nullable Hint hint) {
return Sentry.captureFeedback(feedback, hint);
}

@Override
public @NotNull SentryId captureFeedback(
@NotNull Feedback feedback, @Nullable Hint hint, @Nullable ScopeCallback callback) {
Expand Down
23 changes: 22 additions & 1 deletion sentry/src/main/java/io/sentry/protocol/Feedback.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@
import io.sentry.ObjectReader;
import io.sentry.ObjectWriter;
import io.sentry.SentryLevel;
import io.sentry.util.CollectionUtils;
import io.sentry.vendor.gson.stream.JsonToken;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

Expand Down Expand Up @@ -39,7 +41,7 @@ public Feedback(final @NotNull Feedback feedback) {
this.associatedEventId = feedback.associatedEventId;
this.replayId = feedback.replayId;
this.url = feedback.url;
this.unknown = feedback.unknown;
this.unknown = CollectionUtils.newConcurrentHashMap(feedback.unknown);
}

public @Nullable String getContactEmail() {
Expand Down Expand Up @@ -90,6 +92,25 @@ public void setMessage(final @NotNull String message) {
this.message = message;
}
Comment thread
stefanosiano marked this conversation as resolved.

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Feedback)) return false;
Feedback feedback = (Feedback) o;
return Objects.equals(message, feedback.message)
Comment thread
stefanosiano marked this conversation as resolved.
&& Objects.equals(contactEmail, feedback.contactEmail)
&& Objects.equals(name, feedback.name)
&& Objects.equals(associatedEventId, feedback.associatedEventId)
&& Objects.equals(replayId, feedback.replayId)
&& Objects.equals(url, feedback.url)
&& Objects.equals(unknown, feedback.unknown);
}

@Override
public int hashCode() {
return Objects.hash(message, contactEmail, name, associatedEventId, replayId, url, unknown);
}

// JsonKeys

public static final class JsonKeys {
Expand Down
15 changes: 15 additions & 0 deletions sentry/src/test/java/io/sentry/HubAdapterTest.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.sentry

import io.sentry.protocol.Feedback
import io.sentry.protocol.SentryTransaction
import io.sentry.protocol.User
import io.sentry.test.createSentryClientMock
Expand Down Expand Up @@ -57,6 +58,20 @@ class HubAdapterTest {
verify(scopes).captureMessage(eq("message"), eq(sentryLevel), eq(scopeCallback))
}

@Test fun `captureFeedback calls Hub`() {
val hint = Hint()
val scopeCallback = mock<ScopeCallback>()
val feedback = Feedback("message")
HubAdapter.getInstance().captureFeedback(feedback)
verify(scopes).captureFeedback(eq(feedback))

HubAdapter.getInstance().captureFeedback(feedback, hint)
verify(scopes).captureFeedback(eq(feedback), eq(hint))

HubAdapter.getInstance().captureFeedback(feedback, hint, scopeCallback)
verify(scopes).captureFeedback(eq(feedback), eq(hint), eq(scopeCallback))
}

@Test fun `captureEnvelope calls Hub`() {
val envelope = mock<SentryEnvelope>()
val hint = mock<Hint>()
Expand Down
37 changes: 37 additions & 0 deletions sentry/src/test/java/io/sentry/JsonSerializerTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package io.sentry
import io.sentry.profilemeasurements.ProfileMeasurement
import io.sentry.profilemeasurements.ProfileMeasurementValue
import io.sentry.protocol.Device
import io.sentry.protocol.Feedback
import io.sentry.protocol.ReplayRecordingSerializationTest
import io.sentry.protocol.Request
import io.sentry.protocol.SdkVersion
Expand Down Expand Up @@ -1245,6 +1246,42 @@ class JsonSerializerTest {
)
}

@Test
fun `serializes feedback`() {
val replayId = SentryId("00000000-0000-0000-0000-000000000001")
val eventId = SentryId("00000000-0000-0000-0000-000000000002")
val feedback = Feedback("message")
feedback.name = "name"
feedback.contactEmail = "email"
feedback.url = "url"
feedback.setReplayId(replayId)
feedback.setAssociatedEventId(eventId)
val actual = serializeToString(feedback)
val expected = "{\"message\":\"message\",\"contact_email\":\"email\",\"name\":\"name\",\"associated_event_id\":\"00000000000000000000000000000002\",\"replay_id\":\"00000000000000000000000000000001\",\"url\":\"url\"}"
assertEquals(expected, actual)
}

@Test
fun `deserializes feedback`() {
val json = """{
"message":"message",
"contact_email":"email",
"name":"name",
"associated_event_id":"00000000000000000000000000000002",
"replay_id":"00000000000000000000000000000001",
"url":"url"
}"""
val feedback = fixture.serializer.deserialize(StringReader(json), Feedback::class.java)
val expected = Feedback("message").apply {
name = "name"
contactEmail = "email"
url = "url"
setReplayId(SentryId("00000000-0000-0000-0000-000000000001"))
setAssociatedEventId(SentryId("00000000-0000-0000-0000-000000000002"))
}
assertEquals(expected, feedback)
}

@Test
fun `ser deser replay data`() {
val replayEvent = SentryReplayEventSerializationTest.Fixture().getSut()
Expand Down
4 changes: 4 additions & 0 deletions sentry/src/test/java/io/sentry/NoOpHubTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ class NoOpHubTest {
fun `captureMessage returns empty SentryId`() =
assertEquals(SentryId.EMPTY_ID, sut.captureMessage("message"))

@Test
fun `captureFeedback returns empty SentryId`() =
assertEquals(SentryId.EMPTY_ID, sut.captureFeedback(mock()))

@Test
fun `close does not affect captureEvent`() {
sut.close()
Expand Down
15 changes: 15 additions & 0 deletions sentry/src/test/java/io/sentry/ScopesAdapterTest.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.sentry

import io.sentry.protocol.Feedback
import io.sentry.protocol.SentryTransaction
import io.sentry.protocol.User
import io.sentry.test.createSentryClientMock
Expand Down Expand Up @@ -57,6 +58,20 @@ class ScopesAdapterTest {
verify(scopes).captureMessage(eq("message"), eq(sentryLevel), eq(scopeCallback))
}

@Test fun `captureFeedback calls Scopes`() {
val scopeCallback = mock<ScopeCallback>()
val hint = mock<Hint>()
val feedback = Feedback("message")
ScopesAdapter.getInstance().captureFeedback(feedback)
verify(scopes).captureFeedback(eq(feedback))

ScopesAdapter.getInstance().captureFeedback(feedback, hint)
verify(scopes).captureFeedback(eq(feedback), eq(hint))

ScopesAdapter.getInstance().captureFeedback(feedback, hint, scopeCallback)
verify(scopes).captureFeedback(eq(feedback), eq(hint), eq(scopeCallback))
}

@Test fun `captureEnvelope calls Scopes`() {
val envelope = mock<SentryEnvelope>()
val hint = mock<Hint>()
Expand Down
95 changes: 95 additions & 0 deletions sentry/src/test/java/io/sentry/ScopesTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import io.sentry.clientreport.DiscardReason
import io.sentry.clientreport.DiscardedEvent
import io.sentry.hints.SessionEndHint
import io.sentry.hints.SessionStartHint
import io.sentry.protocol.Feedback
import io.sentry.protocol.SentryId
import io.sentry.protocol.SentryTransaction
import io.sentry.protocol.User
Expand Down Expand Up @@ -606,6 +607,100 @@ class ScopesTest {

//endregion

//region captureFeedback tests
@Test
fun `when captureFeedback is called and message is empty, lastEventId is empty`() {
val (sut, mockClient) = getEnabledScopes()
sut.captureFeedback(Feedback(""))
assertEquals(SentryId.EMPTY_ID, sut.lastEventId)
verify(mockClient, never()).captureFeedback(any(), anyOrNull(), anyOrNull())
}

@Test
fun `when captureFeedback is called on disabled client, do nothing`() {
val (sut, mockClient) = getEnabledScopes()
sut.close()

sut.captureFeedback(mock())
verify(mockClient, never()).captureFeedback(any(), anyOrNull(), anyOrNull())
}

@Test
fun `when captureFeedback is called with a valid message, captureFeedback on the client should be called`() {
val (sut, mockClient) = getEnabledScopes()

sut.captureFeedback(Feedback("test"))
verify(mockClient).captureFeedback(any(), anyOrNull(), anyOrNull())
}

@Test
fun `when captureFeedback is called with a ScopeCallback then the modified scope is sent to the client`() {
val (sut, mockClient) = getEnabledScopes()

sut.captureFeedback(Feedback("test"), null) {
it.setTag("test", "testValue")
}

verify(mockClient).captureFeedback(
any(),
eq(null),
check {
assertEquals("testValue", it.tags["test"])
}
)
}

@Test
fun `when captureFeedback is called with a ScopeCallback then subsequent calls to captureFeedback send the unmodified Scope to the client`() {
val (sut, mockClient) = getEnabledScopes()
val argumentCaptor = argumentCaptor<IScope>()

sut.captureFeedback(Feedback("testMessage"), null) {
it.setTag("test", "testValue")
}

sut.captureFeedback(Feedback("test"))

verify(mockClient, times(2)).captureFeedback(
any(),
anyOrNull(),
argumentCaptor.capture()
)

assertEquals("testValue", argumentCaptor.allValues[0].tags["test"])
assertNull(argumentCaptor.allValues[1].tags["test"])
}

@Test
fun `when captureFeedback is called with a ScopeCallback that crashes then the feedback should still be captured`() {
val (sut, mockClient, logger) = getEnabledScopes()

val exception = Exception("scope callback exception")
sut.captureFeedback(Feedback("Hello World"), null) {
throw exception
}

verify(mockClient).captureFeedback(
any(),
anyOrNull(),
anyOrNull()
)

verify(logger).log(eq(SentryLevel.ERROR), any(), eq(exception))
}

@Test
fun `when captureFeedback is called with a Hint, it is passed to the client`() {
val (sut, mockClient) = getEnabledScopes()
val hint = Hint()

sut.captureFeedback(Feedback("Hello World"), hint)

verify(mockClient).captureFeedback(any(), eq(hint), anyOrNull())
}

//endregion

//region captureException tests
@Test
fun `when captureException is called and exception is null, lastEventId is empty`() {
Expand Down
20 changes: 20 additions & 0 deletions sentry/src/test/java/io/sentry/SentryClientTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import io.sentry.hints.Cached
import io.sentry.hints.DiskFlushNotification
import io.sentry.hints.TransactionEnd
import io.sentry.protocol.Contexts
import io.sentry.protocol.Feedback
import io.sentry.protocol.Mechanism
import io.sentry.protocol.Message
import io.sentry.protocol.Request
Expand Down Expand Up @@ -289,6 +290,25 @@ class SentryClientTest {
assertEquals(SentryLevel.DEBUG, sentEvent!!.level)
}

@Test
fun `when captureFeedback is called, sentry event contains feedback in contexts and header type`() {
var sentEvent: SentryEvent? = null
fixture.sentryOptions.setBeforeSend { e, _ -> sentEvent = e; e }
val sut = fixture.getSut()
sut.captureFeedback(Feedback("message"), null, null)

val sentFeedback = sentEvent!!.contexts.feedback
assertNotNull(sentFeedback)
assertEquals("message", sentFeedback.message)

verify(fixture.transport).send(
check {
assertEquals(SentryItemType.Feedback, it.items.first().header.type)
},
anyOrNull()
)
}

@Test
fun `when event has release, value from options not applied`() {
val event = SentryEvent()
Expand Down
Loading