Skip to content

Commit

Permalink
ISSUE-1042 Implement PublicAsset/get method
Browse files Browse the repository at this point in the history
  • Loading branch information
hung phan committed May 24, 2024
1 parent 1f975a8 commit e939324
Show file tree
Hide file tree
Showing 10 changed files with 507 additions and 3 deletions.
4 changes: 3 additions & 1 deletion tmail-backend/apps/distributed/src/main/conf/jmap.properties
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,6 @@ authentication.strategy.rfc8621=JWTAuthenticationStrategy,BasicAuthenticationStr
calendarEvent.reply.mailTemplateLocation=file://eml-template/

# The supported languages for replying to CalendarEvent emails
calendarEvent.reply.supportedLanguages=en,fr
calendarEvent.reply.supportedLanguages=en,fr

url.prefix=http://localhost
4 changes: 3 additions & 1 deletion tmail-backend/apps/memory/src/main/conf/jmap.properties
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,6 @@ jwt.privatekeypem.url=file://conf/jwt_privatekey
calendarEvent.reply.mailTemplateLocation=file://eml-template/

# The supported languages for replying to CalendarEvent emails
calendarEvent.reply.supportedLanguages=en,fr
calendarEvent.reply.supportedLanguages=en,fr

url.prefix=http://localhost
Original file line number Diff line number Diff line change
@@ -0,0 +1,306 @@
package com.linagora.tmail.james.common

import java.io.ByteArrayInputStream

import com.linagora.tmail.james.common.PublicAssetGetMethodContract.{CREATION_REQUEST, IDENTITY_ID}
import com.linagora.tmail.james.common.probe.PublicAssetProbe
import com.linagora.tmail.james.jmap.publicAsset.ImageContentType.ImageContentType
import com.linagora.tmail.james.jmap.publicAsset.{ImageContentType, PublicAssetCreationRequest, PublicAssetIdFactory}
import io.netty.handler.codec.http.HttpHeaderNames.ACCEPT
import io.restassured.RestAssured.{`given`, requestSpecification}
import io.restassured.http.ContentType.JSON
import net.javacrumbs.jsonunit.JsonMatchers.jsonEquals
import net.javacrumbs.jsonunit.core.Option.IGNORING_ARRAY_ORDER
import net.javacrumbs.jsonunit.core.internal.Options
import org.apache.http.HttpStatus.SC_OK
import org.apache.james.GuiceJamesServer
import org.apache.james.jmap.api.model.Size.Size
import org.apache.james.jmap.api.model.{IdentityId, Size}
import org.apache.james.jmap.core.ResponseObject.SESSION_STATE
import org.apache.james.jmap.http.UserCredential
import org.apache.james.jmap.rfc8621.contract.Fixture.{ACCEPT_RFC8621_VERSION_HEADER, ANDRE, ANDRE_PASSWORD, BOB, BOB_PASSWORD, DOMAIN, authScheme, baseRequestSpecBuilder}
import org.apache.james.mailbox.model.ContentType
import org.apache.james.utils.DataProbeImpl
import org.junit.jupiter.api.{BeforeEach, Test}

object PublicAssetGetMethodContract {
val CONTENT_TYPE: ContentType = ContentType.of("image/png")
val IMAGE_CONTENT_TYPE: ImageContentType = ImageContentType.from(CONTENT_TYPE).toOption.get
val ASSET_CONTENT: Array[Byte] = Array[Byte](1, 2, 3)
val SIZE: Size = Size.sanitizeSize(ASSET_CONTENT.length)
val IDENTITY_ID = IdentityId.generate
val IDENTITY_IDS: Seq[IdentityId] = Seq(IDENTITY_ID)
val CREATION_REQUEST: PublicAssetCreationRequest = PublicAssetCreationRequest(
size = SIZE,
contentType = IMAGE_CONTENT_TYPE,
content = () => new ByteArrayInputStream(ASSET_CONTENT),
identityIds = IDENTITY_IDS)
}

trait PublicAssetGetMethodContract {
@BeforeEach
def setUp(server: GuiceJamesServer): Unit = {
server.getProbe(classOf[DataProbeImpl])
.fluent()
.addDomain(DOMAIN.asString)
.addUser(BOB.asString(), BOB_PASSWORD)
.addUser(ANDRE.asString(), ANDRE_PASSWORD)

requestSpecification = baseRequestSpecBuilder(server)
.setAuth(authScheme(UserCredential(BOB, BOB_PASSWORD)))
.addHeader(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
.build()
}

@Test
def missingPublicAssetCapabilityShouldFail(): Unit =
`given`
.body(
s"""{
| "using": ["urn:ietf:params:jmap:core"],
| "methodCalls": [
| [
| "PublicAsset/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "ids": null
| },
| "c1"
| ]
| ]
|}""".stripMargin)
.when
.post
.`then`
.statusCode(SC_OK)
.contentType(JSON)
.body("", jsonEquals(
s"""{
| "sessionState": "${SESSION_STATE.value}",
| "methodResponses": [
| [
| "error",
| {
| "type": "unknownMethod",
| "description": "Missing capability(ies): com:linagora:params:jmap:public:assets"
| },
| "c1"
| ]
| ]
|}""".stripMargin))


@Test
def getShouldReturnEmptyAssetsByDefault(): Unit =
`given`
.body(
s"""{
| "using": ["urn:ietf:params:jmap:core", "com:linagora:params:jmap:public:assets"],
| "methodCalls": [
| [
| "PublicAsset/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "ids": null
| },
| "c1"
| ]
| ]
|}""".stripMargin)
.when
.post
.`then`
.statusCode(SC_OK)
.contentType(JSON)
.body("methodResponses[0]", jsonEquals(
s"""[
| "PublicAsset/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "state": "2c9f1b12-b35a-43e6-9af2-0106fb53a943",
| "list": [],
| "notFound": []
| },
| "c1"
|]""".stripMargin))

@Test
def fetchNullIdsShouldReturnAllAssets(server: GuiceJamesServer): Unit = {
val publicAsset = server.getProbe(classOf[PublicAssetProbe]).create(BOB, CREATION_REQUEST)
val publicAsset2 = server.getProbe(classOf[PublicAssetProbe]).create(BOB, CREATION_REQUEST)

`given`
.body(
s"""{
| "using": ["urn:ietf:params:jmap:core", "com:linagora:params:jmap:public:assets"],
| "methodCalls": [
| [
| "PublicAsset/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "ids": null
| },
| "c1"
| ]
| ]
|}""".stripMargin)
.when
.post
.`then`
.statusCode(SC_OK)
.contentType(JSON)
.body("methodResponses[0]", jsonEquals(
s"""[
| "PublicAsset/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "state": "$${json-unit.ignore}",
| "list": [
| {
| "id": "${publicAsset.id.value}",
| "publicURI": "${publicAsset.publicURI.value}",
| "size": 3,
| "contentType": "image/png",
| "identityIds": [ "${IDENTITY_ID.id}" ]
| },
| {
| "id": "${publicAsset2.id.value}",
| "publicURI": "${publicAsset2.publicURI.value}",
| "size": 3,
| "contentType": "image/png",
| "identityIds": [ "${IDENTITY_ID.id}" ]
| }
| ],
| "notFound": []
| },
| "c1"
|]""".stripMargin).withOptions(new Options(IGNORING_ARRAY_ORDER)))
}

@Test
def fetchIdsShouldReturnSpecificAssets(server: GuiceJamesServer): Unit = {
val publicAsset = server.getProbe(classOf[PublicAssetProbe]).create(BOB, CREATION_REQUEST)

`given`
.body(
s"""{
| "using": ["urn:ietf:params:jmap:core", "com:linagora:params:jmap:public:assets"],
| "methodCalls": [
| [
| "PublicAsset/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "ids": [ "${publicAsset.id.value}" ]
| },
| "c1"
| ]
| ]
|}""".stripMargin)
.when
.post
.`then`
.statusCode(SC_OK)
.contentType(JSON)
.body("methodResponses[0]", jsonEquals(
s"""[
| "PublicAsset/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "state": "$${json-unit.ignore}",
| "list": [
| {
| "id": "${publicAsset.id.value}",
| "publicURI": "${publicAsset.publicURI.value}",
| "size": 3,
| "contentType": "image/png",
| "identityIds": [ "${IDENTITY_ID.id}" ]
| }
| ],
| "notFound": []
| },
| "c1"
|]""".stripMargin))
}

@Test
def mixedFoundAndNotFoundCase(server: GuiceJamesServer): Unit = {
val publicAsset = server.getProbe(classOf[PublicAssetProbe]).create(BOB, CREATION_REQUEST)
val nonExistedAssetId = PublicAssetIdFactory.generate()

`given`
.body(
s"""{
| "using": ["urn:ietf:params:jmap:core", "com:linagora:params:jmap:public:assets"],
| "methodCalls": [
| [
| "PublicAsset/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "ids": [
| "${publicAsset.id.value}",
| "${nonExistedAssetId.value}",
| "notFound"
| ]
| },
| "c1"
| ]
| ]
|}""".stripMargin)
.when
.post
.`then`
.statusCode(SC_OK)
.contentType(JSON)
.body("methodResponses[0]", jsonEquals(
s"""[
| "PublicAsset/get",
| {
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
| "state": "$${json-unit.ignore}",
| "list": [
| {
| "id": "${publicAsset.id.value}",
| "publicURI": "${publicAsset.publicURI.value}",
| "size": 3,
| "contentType": "image/png",
| "identityIds": [ "${IDENTITY_ID.id}" ]
| }
| ],
| "notFound": [ "notFound", "${nonExistedAssetId.value}" ]
| },
| "c1"
|]""".stripMargin))
}

@Test
def shouldFailWhenWrongAccountId(): Unit =
`given`
.body(
s"""{
| "using": ["urn:ietf:params:jmap:core", "com:linagora:params:jmap:public:assets"],
| "methodCalls": [
| [
| "PublicAsset/get",
| {
| "accountId": "unknownAccountId",
| "ids": null
| },
| "c1"
| ]
| ]
|}""".stripMargin)
.when
.post
.`then`
.statusCode(SC_OK)
.contentType(JSON)
.body("", jsonEquals(
s"""{
| "sessionState": "${SESSION_STATE.value}",
| "methodResponses": [
| ["error", {
| "type": "accountNotFound"
| }, "c1"]
| ]
|}""".stripMargin))
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.linagora.tmail.james.common.probe

import com.google.inject.AbstractModule
import com.google.inject.multibindings.Multibinder
import com.linagora.tmail.james.jmap.publicAsset.{PublicAssetCreationRequest, PublicAssetRepository, PublicAssetStorage}
import jakarta.inject.Inject
import org.apache.james.core.Username
import org.apache.james.utils.GuiceProbe
import reactor.core.scala.publisher.SMono

class PublicAssetProbeModule extends AbstractModule {
override def configure(): Unit =
Multibinder.newSetBinder(binder(), classOf[GuiceProbe])
.addBinding()
.to(classOf[PublicAssetProbe])
}

class PublicAssetProbe @Inject()(publicAssetRepository: PublicAssetRepository) extends GuiceProbe {
def create(username: Username, creationRequest: PublicAssetCreationRequest): PublicAssetStorage =
SMono(publicAssetRepository.create(username, creationRequest)).block()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.linagora.tmail.james;

import static org.apache.james.data.UsersRepositoryModuleChooser.Implementation.DEFAULT;

import org.apache.james.JamesServerBuilder;
import org.apache.james.JamesServerExtension;
import org.junit.jupiter.api.extension.RegisterExtension;

import com.linagora.tmail.james.app.MemoryConfiguration;
import com.linagora.tmail.james.app.MemoryServer;
import com.linagora.tmail.james.common.PublicAssetGetMethodContract;
import com.linagora.tmail.james.common.probe.PublicAssetProbeModule;
import com.linagora.tmail.module.LinagoraTestJMAPServerModule;

public class MemoryPublicAssetGetMethodTest implements PublicAssetGetMethodContract {
@RegisterExtension
static JamesServerExtension jamesServerExtension = new JamesServerBuilder<MemoryConfiguration>(tmpDir ->
MemoryConfiguration.builder()
.workingDirectory(tmpDir)
.configurationFromClasspath()
.usersRepository(DEFAULT)
.build())
.server(configuration -> MemoryServer.createServer(configuration)
.overrideWith(new LinagoraTestJMAPServerModule())
.overrideWith(new PublicAssetProbeModule()))
.build();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.linagora.tmail.james.jmap.json

import com.linagora.tmail.james.jmap.model.{PublicAssetDTO, PublicAssetGetRequest, PublicAssetGetResponse}
import com.linagora.tmail.james.jmap.publicAsset.{PublicAssetId, PublicURI}
import org.apache.james.jmap.api.model.IdentityId
import org.apache.james.jmap.core.UuidState
import play.api.libs.json.{JsResult, JsValue, Json, Reads, Writes}

object PublicAssetSerializer {
private implicit val publicAssetGetReads: Reads[PublicAssetGetRequest] = Json.reads[PublicAssetGetRequest]

private implicit val stateWrites: Writes[UuidState] = Json.valueWrites[UuidState]
private implicit val publicAssetIdWrites: Writes[PublicAssetId] = Json.valueWrites[PublicAssetId]
private implicit val publicAssetURIWrites: Writes[PublicURI] = Json.valueWrites[PublicURI]
private implicit val identityIdWrites: Writes[IdentityId] = Json.valueWrites[IdentityId]
private implicit val publicAssetWrites: Writes[PublicAssetDTO] = Json.writes[PublicAssetDTO]
private implicit val publicAssetResponseWrites: Writes[PublicAssetGetResponse] = Json.writes[PublicAssetGetResponse]

def deserializeGetRequest(input: JsValue): JsResult[PublicAssetGetRequest] = Json.fromJson[PublicAssetGetRequest](input)

def serializeGetResponse(response: PublicAssetGetResponse): JsValue = Json.toJson(response)
}
Loading

0 comments on commit e939324

Please sign in to comment.