-
Notifications
You must be signed in to change notification settings - Fork 14
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
62f04fb
commit 03333fa
Showing
5 changed files
with
280 additions
and
0 deletions.
There are no files selected for viewing
89 changes: 89 additions & 0 deletions
89
...ore/src/main/kotlin/io/embrace/android/embracesdk/internal/logs/attachments/Attachment.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
package io.embrace.android.embracesdk.internal.logs.attachments | ||
|
||
import io.embrace.android.embracesdk.internal.logs.attachments.AttachmentErrorCode.ATTACHMENT_TOO_LARGE | ||
import io.embrace.android.embracesdk.internal.logs.attachments.AttachmentErrorCode.OVER_MAX_ATTACHMENTS | ||
import io.embrace.android.embracesdk.internal.logs.attachments.AttachmentErrorCode.UNKNOWN | ||
import io.embrace.android.embracesdk.internal.utils.toNonNullMap | ||
import java.util.UUID | ||
|
||
/** | ||
* Holds attributes that describe an attachment to a log record. | ||
*/ | ||
internal sealed class Attachment( | ||
val size: Long, | ||
val id: String, | ||
val counter: AttachmentCounter, | ||
) { | ||
|
||
internal companion object { | ||
const val ATTR_KEY_SIZE = "emb.attachment_size" | ||
const val ATTR_KEY_URL = "emb.attachment_url" | ||
const val ATTR_KEY_ID = "emb.attachment_id" | ||
const val ATTR_KEY_ERR_CODE = "emb.attachment_error_code" | ||
private const val LIMIT_MB = 1 * 1024 * 1024 | ||
} | ||
|
||
abstract val attributes: Map<String, String> | ||
|
||
protected fun constructAttributes( | ||
size: Long, | ||
id: String, | ||
errorCode: AttachmentErrorCode? = null | ||
Check warning on line 31 in embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/logs/attachments/Attachment.kt
|
||
) = mapOf( | ||
ATTR_KEY_SIZE to size.toString(), | ||
ATTR_KEY_ID to id, | ||
ATTR_KEY_ERR_CODE to errorCode?.name | ||
).toNonNullMap() | ||
|
||
/** | ||
* An attachment that is uploaded to Embrace's backend. | ||
*/ | ||
class EmbraceHosted( | ||
val bytes: ByteArray, | ||
counter: AttachmentCounter, | ||
) : Attachment( | ||
bytes.size.toLong(), | ||
UUID.randomUUID().toString(), | ||
counter | ||
) { | ||
|
||
private val errorCode: AttachmentErrorCode? = when { | ||
!counter.incrementAndCheckAttachmentLimit() -> OVER_MAX_ATTACHMENTS | ||
size > LIMIT_MB -> ATTACHMENT_TOO_LARGE | ||
else -> null | ||
} | ||
|
||
override val attributes: Map<String, String> = constructAttributes(size, id, errorCode) | ||
} | ||
|
||
/** | ||
* An attachment that is uploaded to a user-supplied backend. | ||
*/ | ||
class UserHosted( | ||
size: Long, | ||
id: String, | ||
val url: String, | ||
counter: AttachmentCounter, | ||
) : Attachment(size, id, counter) { | ||
|
||
private val errorCode: AttachmentErrorCode? = when { | ||
!counter.incrementAndCheckAttachmentLimit() -> OVER_MAX_ATTACHMENTS | ||
size < 0 -> UNKNOWN | ||
url.isEmpty() -> UNKNOWN | ||
isNotUuid() -> UNKNOWN | ||
else -> null | ||
} | ||
|
||
override val attributes: Map<String, String> = | ||
constructAttributes(size, id, errorCode).plus( | ||
ATTR_KEY_URL to url | ||
) | ||
|
||
private fun isNotUuid(): Boolean = try { | ||
UUID.fromString(id) | ||
false | ||
} catch (e: IllegalArgumentException) { | ||
true | ||
} | ||
} | ||
} |
19 changes: 19 additions & 0 deletions
19
.../main/kotlin/io/embrace/android/embracesdk/internal/logs/attachments/AttachmentCounter.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
package io.embrace.android.embracesdk.internal.logs.attachments | ||
|
||
import io.embrace.android.embracesdk.internal.session.MemoryCleanerListener | ||
import java.util.concurrent.atomic.AtomicInteger | ||
|
||
/** | ||
* Counts the number of attachments that should be added to log records. | ||
*/ | ||
class AttachmentCounter(private val limit: Int = 5) : MemoryCleanerListener { | ||
|
||
private val count: AtomicInteger = AtomicInteger(0) | ||
|
||
override fun cleanCollections() = count.set(0) | ||
|
||
/** | ||
* Increments the counter of attachments for this session and returns true if an attachment can be uploaded. | ||
*/ | ||
fun incrementAndCheckAttachmentLimit(): Boolean = count.incrementAndGet() <= limit | ||
} |
11 changes: 11 additions & 0 deletions
11
...ain/kotlin/io/embrace/android/embracesdk/internal/logs/attachments/AttachmentErrorCode.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
package io.embrace.android.embracesdk.internal.logs.attachments | ||
|
||
/** | ||
* Enumerates the states where an attachment could not be added to a log record. | ||
*/ | ||
internal enum class AttachmentErrorCode { | ||
ATTACHMENT_TOO_LARGE, | ||
UNSUCCESSFUL_UPLOAD, | ||
OVER_MAX_ATTACHMENTS, | ||
UNKNOWN | ||
} |
137 changes: 137 additions & 0 deletions
137
...e/src/test/java/io/embrace/android/embracesdk/internal/logs/attachments/AttachmentTest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
package io.embrace.android.embracesdk.internal.logs.attachments | ||
|
||
import io.embrace.android.embracesdk.internal.logs.attachments.Attachment.Companion.ATTR_KEY_ERR_CODE | ||
import io.embrace.android.embracesdk.internal.logs.attachments.Attachment.Companion.ATTR_KEY_ID | ||
import io.embrace.android.embracesdk.internal.logs.attachments.Attachment.Companion.ATTR_KEY_SIZE | ||
import io.embrace.android.embracesdk.internal.logs.attachments.Attachment.Companion.ATTR_KEY_URL | ||
import io.embrace.android.embracesdk.internal.logs.attachments.AttachmentErrorCode.ATTACHMENT_TOO_LARGE | ||
import io.embrace.android.embracesdk.internal.logs.attachments.AttachmentErrorCode.OVER_MAX_ATTACHMENTS | ||
import io.embrace.android.embracesdk.internal.logs.attachments.AttachmentErrorCode.UNKNOWN | ||
import org.junit.Assert.assertEquals | ||
import org.junit.Assert.assertNotNull | ||
import org.junit.Test | ||
import java.util.UUID | ||
|
||
internal class AttachmentTest { | ||
|
||
private companion object { | ||
private const val URL = "https://example.com/my-attachment" | ||
private const val SIZE = 2L | ||
private const val LIMIT_MB = 1 * 1024 * 1024 | ||
private val ID = UUID.randomUUID().toString() | ||
private val BYTES = "{}".toByteArray() | ||
} | ||
|
||
private val counter = AttachmentCounter(limit = Int.MAX_VALUE) | ||
|
||
@Test | ||
fun `create embrace hosted attachment`() { | ||
val attachment = Attachment.EmbraceHosted(BYTES, counter) | ||
attachment.assertEmbraceHostedAttributesMatch() | ||
} | ||
|
||
@Test | ||
fun `embrace hosted attachment empty byte array`() { | ||
val attachment = Attachment.EmbraceHosted(ByteArray(0), counter) | ||
attachment.assertEmbraceHostedAttributesMatch(size = 0) | ||
} | ||
|
||
@Test | ||
fun `embrace hosted attachment at max size`() { | ||
val attachment = Attachment.EmbraceHosted(ByteArray(LIMIT_MB), counter) | ||
attachment.assertEmbraceHostedAttributesMatch(size = LIMIT_MB.toLong()) | ||
} | ||
|
||
@Test | ||
fun `embrace hosted attachment obeys max size constraints`() { | ||
val size = LIMIT_MB + 1 | ||
val attachment = Attachment.EmbraceHosted(ByteArray(size), counter) | ||
attachment.assertEmbraceHostedAttributesMatch(size = size.toLong(), errorCode = ATTACHMENT_TOO_LARGE) | ||
} | ||
|
||
@Test | ||
fun `embrace hosted attachment exceeds session limit`() { | ||
val smallCounter = AttachmentCounter(1) | ||
val attachment = Attachment.EmbraceHosted(BYTES, smallCounter) | ||
attachment.assertEmbraceHostedAttributesMatch() | ||
|
||
val size = LIMIT_MB + 1L | ||
val bytes = ByteArray(size.toInt()) | ||
val limitedAttachment = Attachment.EmbraceHosted(bytes, smallCounter) | ||
limitedAttachment.assertEmbraceHostedAttributesMatch(size = size, errorCode = OVER_MAX_ATTACHMENTS) | ||
} | ||
|
||
@Test | ||
fun `create user hosted attachment`() { | ||
val attachment = Attachment.UserHosted(SIZE, ID, URL, counter) | ||
attachment.assertUserHostedAttributesMatch() | ||
} | ||
|
||
@Test | ||
fun `user hosted attachment empty size`() { | ||
val size: Long = 0 | ||
val attachment = Attachment.UserHosted(size, ID, URL, counter) | ||
attachment.assertUserHostedAttributesMatch(size = size) | ||
} | ||
|
||
@Test | ||
fun `user hosted attachment invalid size`() { | ||
val size: Long = -1 | ||
val attachment = Attachment.UserHosted(size, ID, URL, counter) | ||
attachment.assertUserHostedAttributesMatch(size = size, errorCode = UNKNOWN) | ||
} | ||
|
||
@Test | ||
fun `user hosted attachment invalid url`() { | ||
val url = "" | ||
val attachment = Attachment.UserHosted(SIZE, ID, url, counter) | ||
attachment.assertUserHostedAttributesMatch(url = url, errorCode = UNKNOWN) | ||
} | ||
|
||
@Test | ||
fun `user hosted attachment invalid ID`() { | ||
val id = "my-id" | ||
val attachment = Attachment.UserHosted(SIZE, id, URL, counter) | ||
attachment.assertUserHostedAttributesMatch(id = id, errorCode = UNKNOWN) | ||
} | ||
|
||
@Test | ||
fun `user hosted attachment has no max size constraints`() { | ||
val size = 5000000L // 50MiB | ||
val attachment = Attachment.UserHosted(size, ID, URL, counter) | ||
attachment.assertUserHostedAttributesMatch(size = size) | ||
} | ||
|
||
@Test | ||
fun `user hosted attachment exceeds session limit`() { | ||
val smallCounter = AttachmentCounter(1) | ||
val attachment = Attachment.UserHosted(SIZE, ID, URL, smallCounter) | ||
attachment.assertUserHostedAttributesMatch() | ||
|
||
val size = -1L | ||
val limitedAttachment = Attachment.UserHosted(size, ID, URL, smallCounter) | ||
limitedAttachment.assertUserHostedAttributesMatch(size = size, errorCode = OVER_MAX_ATTACHMENTS) | ||
} | ||
|
||
private fun Attachment.assertEmbraceHostedAttributesMatch( | ||
size: Long = SIZE, | ||
errorCode: AttachmentErrorCode? = null, | ||
) { | ||
val observedId = checkNotNull(attributes[ATTR_KEY_ID]) | ||
assertNotNull(UUID.fromString(observedId)) | ||
assertEquals(size, checkNotNull(attributes[ATTR_KEY_SIZE]).toLong()) | ||
assertEquals(errorCode?.toString(), attributes[ATTR_KEY_ERR_CODE]) | ||
} | ||
|
||
private fun Attachment.assertUserHostedAttributesMatch( | ||
size: Long = SIZE, | ||
url: String = URL, | ||
id: String = ID, | ||
errorCode: AttachmentErrorCode? = null, | ||
) { | ||
assertEquals(size, checkNotNull(attributes[ATTR_KEY_SIZE]).toLong()) | ||
assertEquals(id, checkNotNull(attributes[ATTR_KEY_ID])) | ||
assertEquals(errorCode?.toString(), attributes[ATTR_KEY_ERR_CODE]) | ||
assertEquals(url, checkNotNull(attributes[ATTR_KEY_URL])) | ||
} | ||
} |
24 changes: 24 additions & 0 deletions
24
...est/java/io/embrace/android/embracesdk/internal/logs/attachments/AttachmentCounterTest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
package io.embrace.android.embracesdk.internal.logs.attachments | ||
|
||
import org.junit.Assert.assertFalse | ||
import org.junit.Assert.assertTrue | ||
import org.junit.Test | ||
|
||
internal class AttachmentCounterTest { | ||
|
||
@Test | ||
fun `limit exceeded`() { | ||
val counter = AttachmentCounter() | ||
assertLimitRespected(counter) | ||
|
||
counter.cleanCollections() | ||
assertLimitRespected(counter) | ||
} | ||
|
||
private fun assertLimitRespected(counter: AttachmentCounter) { | ||
repeat(5) { | ||
assertTrue(counter.incrementAndCheckAttachmentLimit()) | ||
} | ||
assertFalse(counter.incrementAndCheckAttachmentLimit()) | ||
} | ||
} |