Skip to content

Commit

Permalink
(feature) Adding attachment max size
Browse files Browse the repository at this point in the history
  • Loading branch information
craigatk committed Mar 1, 2020
1 parent bc44398 commit 368e9b0
Show file tree
Hide file tree
Showing 13 changed files with 139 additions and 54 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,6 @@ class ObjectStoreClient(private val config: ObjectStoreConfig) {

fun bucketExists(bucketName: String) = minioClient.bucketExists(bucketName)

fun putObject(bucketName: String, objectName: String, localFilePath: String) {
minioClient.putObject(bucketName, objectName, localFilePath)
}

fun putObject(bucketName: String, objectName: String, stream: InputStream) {
minioClient.putObject(bucketName, objectName, stream, "application/octet-stream")
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
package projektor.objectstore

import io.kotlintest.specs.StringSpec
import io.minio.errors.ErrorResponseException
import java.io.File
import strikt.api.expectThat
import strikt.assertions.containsIgnoringCase
import strikt.assertions.isEqualTo
import strikt.assertions.isNotNull
import strikt.assertions.isNull
Expand All @@ -20,7 +19,7 @@ class ObjectStoreClientObjectSpec : StringSpec() {

init {
"should store and retrieve object" {
client.putObject(bucketName, objectName, "src/test/resources/test_file.txt")
client.putObject(bucketName, objectName, File("src/test/resources/test_file.txt").inputStream())

expectThat(client.getObject(bucketName, objectName))
.isNotNull()
Expand All @@ -33,17 +32,11 @@ class ObjectStoreClientObjectSpec : StringSpec() {
}

"should delete object"() {
client.putObject(bucketName, objectName, "src/test/resources/test_file.txt")
client.putObject(bucketName, objectName, File("src/test/resources/test_file.txt").inputStream())

client.removeObject(bucketName, objectName)

try {
client.getObject(bucketName, objectName)
} catch (e: ErrorResponseException) {
expectThat(e.message).isNotNull().and {
containsIgnoringCase("does not exist")
}
}
expectThat(client.getObject(bucketName, objectName)).isNull()
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package projektor.server.api

data class AddAttachmentResponse(
val successful: Boolean,
val name: String?,
val error: AddAttachmentError?
)

enum class AddAttachmentError {
ATTACHMENTS_DISABLED,
ATTACHMENT_TOO_LARGE
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package projektor.attachment

import io.ktor.util.KtorExperimentalAPI
import java.io.InputStream
import java.math.BigDecimal
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.slf4j.LoggerFactory
Expand All @@ -12,7 +13,10 @@ import projektor.server.api.Attachment
import projektor.server.api.PublicId

@KtorExperimentalAPI
class AttachmentService(private val config: AttachmentStoreConfig, private val attachmentRepository: AttachmentRepository) {
class AttachmentService(
private val config: AttachmentStoreConfig,
private val attachmentRepository: AttachmentRepository
) {
private val logger = LoggerFactory.getLogger(javaClass.canonicalName)

private val objectStoreClient = ObjectStoreClient(ObjectStoreConfig(config.url, config.accessKey, config.secretKey))
Expand All @@ -27,6 +31,12 @@ class AttachmentService(private val config: AttachmentStoreConfig, private val a
}
}

fun attachmentSizeValid(attachmentSizeInBytes: BigDecimal?): Boolean {
val maxSizeInBytes = config.maxSizeMB?.let { it * BigDecimal.valueOf(1024) * BigDecimal.valueOf(1024) }

return maxSizeInBytes == null || attachmentSizeInBytes == null || attachmentSizeInBytes <= maxSizeInBytes
}

suspend fun addAttachment(publicId: PublicId, fileName: String, attachmentStream: InputStream) {
val objectName = attachmentObjectName(publicId, fileName)

Expand All @@ -41,9 +51,7 @@ class AttachmentService(private val config: AttachmentStoreConfig, private val a
objectStoreClient.getObject(config.bucketName, attachmentObjectName(publicId, attachmentFileName))
}

suspend fun listAttachments(publicId: PublicId): List<Attachment> = withContext(Dispatchers.IO) {
attachmentRepository.listAttachments(publicId)
}
suspend fun listAttachments(publicId: PublicId): List<Attachment> = attachmentRepository.listAttachments(publicId)

companion object {
fun attachmentObjectName(publicId: PublicId, attachmentFileName: String) = "${publicId.id}-$attachmentFileName"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,16 @@ package projektor.attachment

import io.ktor.config.ApplicationConfig
import io.ktor.util.KtorExperimentalAPI
import java.math.BigDecimal

@KtorExperimentalAPI
data class AttachmentStoreConfig(
val url: String,
val bucketName: String,
val autoCreateBucket: Boolean,
val accessKey: String,
val secretKey: String
val secretKey: String,
val maxSizeMB: BigDecimal?
) {
companion object {
fun attachmentStoreEnabled(applicationConfig: ApplicationConfig): Boolean =
Expand All @@ -20,7 +22,8 @@ data class AttachmentStoreConfig(
applicationConfig.property("ktor.attachment.bucketName").getString(),
applicationConfig.propertyOrNull("ktor.attachment.autoCreateBucket")?.getString()?.toBoolean() ?: false,
applicationConfig.property("ktor.attachment.accessKey").getString(),
applicationConfig.property("ktor.attachment.secretKey").getString()
applicationConfig.property("ktor.attachment.secretKey").getString(),
applicationConfig.propertyOrNull("ktor.attachment.maxSizeMB")?.getString()?.toBigDecimal()
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@ import io.ktor.util.getOrFail
import projektor.attachment.AttachmentService
import projektor.auth.AuthConfig
import projektor.auth.AuthService
import projektor.server.api.AddAttachmentError
import projektor.server.api.AddAttachmentResponse
import projektor.server.api.Attachments
import projektor.server.api.CreateAttachmentResponse
import projektor.server.api.PublicId

@KtorExperimentalAPI
Expand All @@ -28,14 +29,20 @@ fun Route.attachments(
val attachmentName = call.parameters.getOrFail("attachmentName")
val attachmentStream = call.receiveStream()

val contentLengthInBytes = call.request.header("content-length")?.toBigDecimal()

if (!authService.isAuthValid(call.request.header(AuthConfig.PublishToken))) {
call.respond(HttpStatusCode.Unauthorized)
} else if (attachmentService != null) {
attachmentService.addAttachment(PublicId(publicId), attachmentName, attachmentStream)
if (attachmentService.attachmentSizeValid(contentLengthInBytes)) {
attachmentService.addAttachment(PublicId(publicId), attachmentName, attachmentStream)

call.respond(HttpStatusCode.OK, CreateAttachmentResponse(true, true, attachmentName))
call.respond(HttpStatusCode.OK, AddAttachmentResponse(true, attachmentName, null))
} else {
call.respond(HttpStatusCode.BadRequest, AddAttachmentResponse(false, null, AddAttachmentError.ATTACHMENT_TOO_LARGE))
}
} else {
call.respond(HttpStatusCode.BadRequest, CreateAttachmentResponse(false, false, null))
call.respond(HttpStatusCode.BadRequest, AddAttachmentResponse(false, null, AddAttachmentError.ATTACHMENTS_DISABLED))
}
}

Expand Down
1 change: 1 addition & 0 deletions server/server-app/src/main/resources/application.conf
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,6 @@ ktor {
autoCreateBucket = ${?ATTACHMENT_AUTO_CREATE_BUCKET}
accessKey = ${?ATTACHMENT_ACCESS_KEY}
secretKey = ${?ATTACHMENT_SECRET_KEY}
maxSizeMB = ${?ATTACHMENT_MAX_SIZE_MB}
}
}
31 changes: 9 additions & 22 deletions server/server-app/src/test/kotlin/projektor/ApplicationTestCase.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,11 @@ import com.zaxxer.hikari.HikariDataSource
import io.ktor.application.Application
import io.ktor.config.MapApplicationConfig
import io.ktor.util.KtorExperimentalAPI
import java.math.BigDecimal
import kotlin.test.AfterTest
import org.jooq.DSLContext
import org.koin.ktor.ext.get
import projektor.attachment.AttachmentStoreConfig
import projektor.database.generated.tables.daos.*
import projektor.objectstore.ObjectStoreClient
import projektor.objectstore.ObjectStoreConfig
import projektor.parser.ResultsXmlLoader

@KtorExperimentalAPI
Expand Down Expand Up @@ -44,20 +42,8 @@ open class ApplicationTestCase {

protected var publishToken: String? = null

protected var assetStoreEnabled: Boolean? = null
private val attachmentStoreConfig = AttachmentStoreConfig(
"http://localhost:9000",
"addassettestbucket",
true,
"minio_access_key",
"minio_secret_key"
)

protected val objectStoreClient = ObjectStoreClient(ObjectStoreConfig(
attachmentStoreConfig.url,
attachmentStoreConfig.accessKey,
attachmentStoreConfig.secretKey
))
protected var attachmentsEnabled: Boolean? = null
protected var attachmentsMaxSizeMB: BigDecimal? = null

fun createTestApplication(application: Application) {
val schema = databaseSchema
Expand All @@ -71,12 +57,13 @@ open class ApplicationTestCase {

publishToken?.let { put("ktor.auth.publishToken", it) }

assetStoreEnabled?.let {
put("ktor.attachment.url", attachmentStoreConfig.url)
put("ktor.attachment.bucketName", attachmentStoreConfig.bucketName)
attachmentsEnabled?.let {
put("ktor.attachment.url", "http://localhost:9000")
put("ktor.attachment.bucketName", "attachmentstesting")
put("ktor.attachment.autoCreateBucket", "true")
put("ktor.attachment.accessKey", attachmentStoreConfig.accessKey)
put("ktor.attachment.secretKey", attachmentStoreConfig.secretKey)
put("ktor.attachment.accessKey", "minio_access_key")
put("ktor.attachment.secretKey", "minio_secret_key")
attachmentsMaxSizeMB?.let { put("ktor.attachment.maxSizeMB", it.toString()) }
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ class AddAttachmentApplicationTest : ApplicationTestCase() {
@Test
fun `should add attachment to test run then get it`() {
val publicId = randomPublicId()
assetStoreEnabled = true
attachmentsEnabled = true

withTestApplication(::createTestApplication) {
handleRequest(HttpMethod.Post, "/run/$publicId/attachments/test-attachment.txt") {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package projektor.attachment

import io.ktor.http.HttpMethod
import io.ktor.http.HttpStatusCode
import io.ktor.server.testing.handleRequest
import io.ktor.server.testing.setBody
import io.ktor.server.testing.withTestApplication
import io.ktor.util.KtorExperimentalAPI
import java.io.File
import java.math.BigDecimal
import kotlin.test.*
import projektor.ApplicationTestCase
import projektor.TestSuiteData
import projektor.incomingresults.randomPublicId
import projektor.server.api.AddAttachmentError
import projektor.server.api.AddAttachmentResponse
import strikt.api.expectThat
import strikt.assertions.isEqualTo
import strikt.assertions.isNotNull

@KtorExperimentalAPI
class AddAttachmentMaxSizeApplicationTest : ApplicationTestCase() {
@Test
fun `when attachment max size configured and attachment size is over max allowed size should return error`() {
val publicId = randomPublicId()
attachmentsEnabled = true
attachmentsMaxSizeMB = BigDecimal("0.02")

withTestApplication(::createTestApplication) {
handleRequest(HttpMethod.Post, "/run/$publicId/attachments/test-run-summary.png") {
testRunDBGenerator.createTestRun(
publicId,
listOf(
TestSuiteData("testSuite1",
listOf("testSuite1TestCase1", "testSuite1TestCase2"),
listOf(),
listOf()
)
)
)

addHeader("content-length", "23342")
setBody(File("src/test/resources/test-run-summary.png").readBytes())
}.apply {
expectThat(response.status()).isEqualTo(HttpStatusCode.BadRequest)

val errorResponse = objectMapper.readValue(response.content, AddAttachmentResponse::class.java)
expectThat(errorResponse.error).isNotNull().and { isEqualTo(AddAttachmentError.ATTACHMENT_TOO_LARGE) }
}
}
}

@Test
fun `when attachment max size configured and attachment size is under max allowed size should succeed`() {
val publicId = randomPublicId()
attachmentsEnabled = true
attachmentsMaxSizeMB = BigDecimal("0.04")

withTestApplication(::createTestApplication) {
handleRequest(HttpMethod.Post, "/run/$publicId/attachments/test-run-summary.png") {
testRunDBGenerator.createTestRun(
publicId,
listOf(
TestSuiteData("testSuite1",
listOf("testSuite1TestCase1", "testSuite1TestCase2"),
listOf(),
listOf()
)
)
)

addHeader("content-length", "23342")
setBody(File("src/test/resources/test-run-summary.png").readBytes())
}.apply {
expectThat(response.status()).isEqualTo(HttpStatusCode.OK)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import io.ktor.http.HttpStatusCode
import io.ktor.server.testing.handleRequest
import io.ktor.server.testing.setBody
import io.ktor.server.testing.withTestApplication
import io.ktor.util.KtorExperimentalAPI
import java.io.File
import kotlin.test.*
import projektor.ApplicationTestCase
Expand All @@ -14,6 +15,7 @@ import projektor.incomingresults.randomPublicId
import strikt.api.expectThat
import strikt.assertions.isEqualTo

@KtorExperimentalAPI
@ExperimentalStdlibApi
class AddAttachmentTokenApplicationTest : ApplicationTestCase() {

Expand All @@ -23,7 +25,7 @@ class AddAttachmentTokenApplicationTest : ApplicationTestCase() {
publishToken = validPublishToken

val publicId = randomPublicId()
assetStoreEnabled = true
attachmentsEnabled = true

withTestApplication(::createTestApplication) {
handleRequest(HttpMethod.Post, "/run/$publicId/attachments/test-attachment.txt") {
Expand Down Expand Up @@ -59,7 +61,7 @@ class AddAttachmentTokenApplicationTest : ApplicationTestCase() {
publishToken = validPublishToken

val publicId = randomPublicId()
assetStoreEnabled = true
attachmentsEnabled = true

withTestApplication(::createTestApplication) {
handleRequest(HttpMethod.Post, "/run/$publicId/attachments/test-attachment.txt") {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ class ListAttachmentsApplicationTest : ApplicationTestCase() {
@Test
fun `should add attachments to test run then list them`() {
val publicId = randomPublicId()
assetStoreEnabled = true
attachmentsEnabled = true

withTestApplication(::createTestApplication) {
handleRequest(HttpMethod.Post, "/run/$publicId/attachments/test-attachment.txt") {
Expand Down

0 comments on commit 368e9b0

Please sign in to comment.