Writing a test case for file uploads in Play 2.1 and Scala

后端 未结 7 1809
猫巷女王i
猫巷女王i 2021-01-02 05:39

I found the following question/answer:

Test MultipartFormData in Play 2.0 FakeRequest

But it seems things have changed in Play 2.1. I\'ve tried adapting the

相关标签:
7条回答
  • 2021-01-02 05:59

    I've modified Alex's code to act as a Writable which better integrates into Play 2.2.2

    package test
    
    import play.api.http._
    import play.api.mvc.MultipartFormData.FilePart
    import play.api.libs.iteratee._
    import play.api.libs.Files.TemporaryFile
    import play.api.mvc.{Codec, MultipartFormData }
    import java.io.{FileInputStream, ByteArrayOutputStream}
    import org.apache.commons.io.IOUtils
    import org.apache.http.entity.mime.MultipartEntity
    import org.apache.http.entity.mime.content._
    
    object MultipartWriteable {
    
      /**
       * `Writeable` for multipart/form-data.
       *
       */
      implicit def writeableOf_multiPartFormData(implicit codec: Codec): Writeable[MultipartFormData[TemporaryFile]] = {
    
        val entity = new MultipartEntity()
    
        def transform(multipart: MultipartFormData[TemporaryFile]):Array[Byte] = {
    
          multipart.dataParts.foreach { part =>
            part._2.foreach { p2 =>
                entity.addPart(part._1, new StringBody(p2))
            }
          }
    
          multipart.files.foreach { file =>
            val part = new FileBody(file.ref.file, file.filename,     file.contentType.getOrElse("application/octet-stream"), null)
            entity.addPart(file.key, part)
          }
    
          val outputStream = new ByteArrayOutputStream
          entity.writeTo(outputStream)
          val bytes = outputStream.toByteArray
          outputStream.close
          bytes
        }
    
        new Writeable[MultipartFormData[TemporaryFile]](transform, Some(entity.getContentType.getValue))
      }
    }
    

    This way it is possible to write something like this:

    val filePart:MultipartFormData.FilePart[TemporaryFile] = MultipartFormData.FilePart(...)
    val fileParts:Seq[MultipartFormData.FilePart[TemporaryFile]] = Seq(filePart)
    val dataParts:Map[String, Seq[String]] = ...
    val multipart = new MultipartFormData[TemporaryFile](dataParts, fileParts, List(), List())
    val request = FakeRequest(POST, "/url", FakeHeaders(), multipart)
    
    var result = route(request).get
    
    0 讨论(0)
  • 2021-01-02 06:00

    In Play 2.6.x you can write test cases in the following way to test file upload API:

    class HDFSControllerTest extends Specification {
      "HDFSController" should {
        "return 200 Status for file Upload" in new WithApplication {
    
          val tempFile = SingletonTemporaryFileCreator.create("txt","csv")
          tempFile.deleteOnExit()
    
          val data = new MultipartFormData[TemporaryFile](Map(),
          List(FilePart("metadata", "text1.csv", Some("text/plain"), tempFile)), List())
    
          val res: Option[Future[Result]] = route(app, FakeRequest(POST, "/api/hdfs").withMultipartFormDataBody(data))
          print(contentAsString(res.get))
          res must beSome.which(status(_) == OK)
       }
      }
    }
    
    0 讨论(0)
  • 2021-01-02 06:01

    Following EEColor's suggestion, I got the following to work:

    "Upload Photo" in {
    
    
        val file = scala.io.Source.fromFile(getClass().getResource("/photos/DSC03024.JPG").getFile())(scala.io.Codec.ISO8859).map(_.toByte).toArray
    
        val data = new MultipartFormData(Map(), List(
        FilePart("qqfile", "DSC03024.JPG", Some("image/jpeg"),
            file)
        ), List())
    
        val result = controllers.Photo.upload()(FakeRequest(POST, "/admin/photos/upload",FakeHeaders(),data))
    
        status(result) must equalTo(CREATED)
        headers(result) must haveKeys(LOCATION)
        contentType(result) must beSome("application/json")      
    
    
      }
    
    0 讨论(0)
  • 2021-01-02 06:07

    Play 2.3 includes a newer version of httpmime.jar, requiring some minor corrections. Building on Marcus's solution using Play's Writeable mechanism, while retaining some of the syntactic sugar from my Play 2.1 solution, this is what I've come up with:

    import scala.language.implicitConversions
    
    import java.io.{ByteArrayOutputStream, File}
    
    import org.apache.http.entity.ContentType
    import org.apache.http.entity.mime.MultipartEntityBuilder
    import org.apache.http.entity.mime.content._
    import org.specs2.mutable.Specification
    
    import play.api.http._
    import play.api.libs.Files.TemporaryFile
    import play.api.mvc.MultipartFormData.FilePart
    import play.api.mvc.{Codec, MultipartFormData}
    import play.api.test.Helpers._
    import play.api.test.{FakeApplication, FakeRequest}
    
    trait FakeMultipartUpload {
      implicit def writeableOf_multiPartFormData(implicit codec: Codec): Writeable[MultipartFormData[TemporaryFile]] = {
        val builder = MultipartEntityBuilder.create().setBoundary("12345678")
    
        def transform(multipart: MultipartFormData[TemporaryFile]): Array[Byte] = {
          multipart.dataParts.foreach { part =>
            part._2.foreach { p2 =>
              builder.addPart(part._1, new StringBody(p2, ContentType.create("text/plain", "UTF-8")))
            }
          }
          multipart.files.foreach { file =>
            val part = new FileBody(file.ref.file, ContentType.create(file.contentType.getOrElse("application/octet-stream")), file.filename)
            builder.addPart(file.key, part)
          }
    
          val outputStream = new ByteArrayOutputStream
          builder.build.writeTo(outputStream)
          outputStream.toByteArray
        }
    
        new Writeable[MultipartFormData[TemporaryFile]](transform, Some(builder.build.getContentType.getValue))
      }
    
      /** shortcut for generating a MultipartFormData with one file part which more fields can be added to */
      def fileUpload(key: String, file: File, contentType: String): MultipartFormData[TemporaryFile] = {
        MultipartFormData(
          dataParts = Map(),
          files = Seq(FilePart[TemporaryFile](key, file.getName, Some(contentType), TemporaryFile(file))),
          badParts = Seq(),
          missingFileParts = Seq())
      }
    
      /** shortcut for a request body containing a single file attachment */
      case class WrappedFakeRequest[A](fr: FakeRequest[A]) {
        def withFileUpload(key: String, file: File, contentType: String) = {
          fr.withBody(fileUpload(key, file, contentType))
        }
      }
      implicit def toWrappedFakeRequest[A](fr: FakeRequest[A]) = WrappedFakeRequest(fr)
    }
    
    class MyTest extends Specification with FakeMultipartUpload {
      "uploading" should {
        "be easier than this" in {
          running(FakeApplication()) {
            val uploadFile = new File("/tmp/file.txt")
            val req = FakeRequest(POST, "/upload/path").
              withFileUpload("image", uploadFile, "image/gif")
            val response = route(req).get
            status(response) must equalTo(OK)
          }
        }
      }
    }
    
    0 讨论(0)
  • 2021-01-02 06:11

    I managed to get this working with Play 2.1 based on various mailing list suggestions. Here's how I do it:

    import scala.language.implicitConversions
    
    import java.io.{ ByteArrayOutputStream, File }
    
    import org.apache.http.entity.mime.MultipartEntity
    import org.apache.http.entity.mime.content.{ ContentBody, FileBody }
    import org.specs2.mutable.Specification
    
    import play.api.http.Writeable
    import play.api.test.{ FakeApplication, FakeRequest }
    import play.api.test.Helpers._
    
    trait FakeMultipartUpload {
      case class WrappedFakeRequest[A](fr: FakeRequest[A]) {
        def withMultipart(parts: (String, ContentBody)*) = {
          // create a multipart form
          val entity = new MultipartEntity()
          parts.foreach { part =>
            entity.addPart(part._1, part._2)
          }
    
          // serialize the form
          val outputStream = new ByteArrayOutputStream
          entity.writeTo(outputStream)
          val bytes = outputStream.toByteArray
    
          // inject the form into our request
          val headerContentType = entity.getContentType.getValue
          fr.withBody(bytes).withHeaders(CONTENT_TYPE -> headerContentType)
        }
    
        def withFileUpload(fileParam: String, file: File, contentType: String) = {
          withMultipart(fileParam -> new FileBody(file, contentType))
        }
      }
    
      implicit def toWrappedFakeRequest[A](fr: FakeRequest[A]) = WrappedFakeRequest(fr)
    
      // override Play's equivalent Writeable so that the content-type header from the FakeRequest is used instead of application/octet-stream  
      implicit val wBytes: Writeable[Array[Byte]] = Writeable(identity, None)
    }
    
    class MyTest extends Specification with FakeMultipartUpload {
      "uploading" should {
        "be easier than this" in {
          running(FakeApplication()) {
            val uploadFile = new File("/tmp/file.txt")
            val req = FakeRequest(POST, "/upload/path").
              withFileUpload("image", uploadFile, "image/gif")
            val response = route(req).get
            status(response) must equalTo(OK)
          }
        }
      }
    }
    
    0 讨论(0)
  • 2021-01-02 06:14

    Here's my version of Writeable[AnyContentAsMultipartFormData]:

    import java.io.File
    
    import play.api.http.{HeaderNames, Writeable}
    import play.api.libs.Files.TemporaryFile
    import play.api.mvc.MultipartFormData.FilePart
    import play.api.mvc.{AnyContentAsMultipartFormData, Codec, MultipartFormData}
    
    object MultipartFormDataWritable {
      val boundary = "--------ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"
    
      def formatDataParts(data: Map[String, Seq[String]]) = {
        val dataParts = data.flatMap { case (key, values) =>
          values.map { value =>
            val name = s""""$key""""
            s"--$boundary\r\n${HeaderNames.CONTENT_DISPOSITION}: form-data; name=$name\r\n\r\n$value\r\n"
          }
        }.mkString("")
        Codec.utf_8.encode(dataParts)
      }
    
      def filePartHeader(file: FilePart[TemporaryFile]) = {
        val name = s""""${file.key}""""
        val filename = s""""${file.filename}""""
        val contentType = file.contentType.map { ct =>
          s"${HeaderNames.CONTENT_TYPE}: $ct\r\n"
        }.getOrElse("")
        Codec.utf_8.encode(s"--$boundary\r\n${HeaderNames.CONTENT_DISPOSITION}: form-data; name=$name; filename=$filename\r\n$contentType\r\n")
      }
    
      val singleton = Writeable[MultipartFormData[TemporaryFile]](
        transform = { form: MultipartFormData[TemporaryFile] =>
          formatDataParts(form.dataParts) ++
            form.files.flatMap { file =>
              val fileBytes = Files.readAllBytes(Paths.get(file.ref.file.getAbsolutePath))
              filePartHeader(file) ++ fileBytes ++ Codec.utf_8.encode("\r\n")
            } ++
            Codec.utf_8.encode(s"--$boundary--")
        },
        contentType = Some(s"multipart/form-data; boundary=$boundary")
      )
    }
    
    implicit val anyContentAsMultipartFormWritable: Writeable[AnyContentAsMultipartFormData] = {
      MultipartFormDataWritable.singleton.map(_.mdf)
    }
    

    It's adapted from (and some bugs fixed): https://github.com/jroper/playframework/blob/multpart-form-data-writeable/framework/src/play/src/main/scala/play/api/http/Writeable.scala#L108

    See the whole post here, if you are interested: http://tech.fongmun.com/post/125479939452/test-multipartformdata-in-play

    0 讨论(0)
提交回复
热议问题