1

I'm using Play 2.2.2 and have been unsuccessfully trying to add tests to my file upload functionality. I know there are plenty of questions about this, I have checked many but either they don't work or the API is deprecated and doesn't work or it fails with some spurious error.

This is how a valid request looks like from the browser (using Firefox's WebDeveloper plugin):

-----------------------------479326525221683770414613115
Content-Disposition: form-data; name="files[]"; filename="upload_aum_sample.csv"
Content-Type: text/csv

AccountName,AuM,Name
IX_CH1,10,A
IX_CH2,20,B
IX_CH3,30,C
IX_CH4,40,D
IX_CH5,50,E
IX_CH6,60,F
IX_CH7,70,G
IX_CH8,80,H

-----------------------------479326525221683770414613115--

This is what I have tried so far without success:


Compiles with a warning i.e. that routeAndCall is deprecated:

"upload file correctly" in new WithApplication {
  val fileName = "upload_aum_sample.csv"
  val file = getClass().getResource(fileName).getFile()
  val data = MultipartFormData(Map(), List(FilePart("files[]", fileName, Some("text/csv"), file)), List(), List())
  val result = routeAndCall(FakeRequest(POST, "/aum/upload/do", FakeHeaders(), data).withSession("username" -> "Test")).get    
  status(result) must equalTo(OK)
  contentType(result) must beSome.which(_ == "application/json")
}

and results in the Exception:

[info] ! upload file correctly
[error]   MatchError: <function1> (of class play.core.Router$Routes$$anon$4) (Helpers.scala:187)
[error] play.api.test.RouteInvokers$$anonfun$routeAndCall$1.apply(Helpers.scala:187)
[error] play.api.test.RouteInvokers$$anonfun$routeAndCall$1.apply(Helpers.scala:187)
[error] play.api.test.RouteInvokers$class.routeAndCall(Helpers.scala:187)
[error] AumUploadPageSpec.routeAndCall(AumUploadPageSpec.scala:30)
[error] play.api.test.RouteInvokers$class.routeAndCall(Helpers.scala:178)
[error] AumUploadPageSpec.routeAndCall(AumUploadPageSpec.scala:30)
[error] AumUploadPageSpec$$anonfun$12$$anon$3$delayedInit$body.apply(AumUploadPageSpec.scala:73)
[error] play.api.test.WithApplication$$anonfun$around$1.apply(Specs.scala:20)
[error] play.api.test.WithApplication$$anonfun$around$1.apply(Specs.scala:20)
[error] play.api.test.PlayRunners$class.running(Helpers.scala:45)
[error] play.api.test.Helpers$.running(Helpers.scala:364)
[error] play.api.test.WithApplication.around(Specs.scala:20)
[error] play.api.test.WithApplication.delayedInit(Specs.scala:17)
[error] AumUploadPageSpec$$anonfun$12$$anon$3.<init>(AumUploadPageSpec.scala:48)
[error] AumUploadPageSpec$$anonfun$12.apply(AumUploadPageSpec.scala:48)
[error] AumUploadPageSpec$$anonfun$12.apply(AumUploadPageSpec.scala:48)

Compiles fine no warnings

"upload file correctly" in new WithApplication {
  val fileName = "upload_aum_sample.csv"
  val file = getClass().getResource(fileName).getFile()
  val data = MultipartFormData(Map(), List(FilePart("files[]", fileName, Some("text/csv"), file)), List(), List())
  val result = controllers.Application.uploadDo("aum")(FakeRequest(POST, "/aum/upload/do", FakeHeaders(), data).withSession("username" -> "Test")).run
  status(result) must equalTo(OK) // <<<<<<< test fails here 
  contentType(result) must beSome.which(_ == "application/json")
}

But the test fails due to the server responding 400 instead of 200 i.e. not OK

[info] x upload file correctly
[error]  '400' is not equal to '200' (AumUploadPageSpec.scala:53)

UPDATE 1: If embed the file content rather than the file I still get the same errors i.e. Change

val file = getClass().getResource(fileName).getFile()

to

val file = scala.io.Source.fromFile(getClass().getResource(fileName).getFile()).map(_.toByte).toArray

UPDATE 2: These are the routing and server side code respectively:

# Generic controllers
POST  /:context/upload/do  controllers.Application.uploadDo(context: String)

//------------------------------------------------------------------------
/**
 * Action that uploads a file for a given context
 * @param context the input context
 */
def uploadDo(context: String) = Action(parse.multipartFormData) { implicit request ⇒
 request.body.file("files[]").map { file ⇒
   val filename = file.filename
   val contentType = file.contentType
 }
 Ok(Json.parse(
   """{"files": [
    {
      "name": "picture1.jpg",
      "size": 902604,
      "error": "Filetype not allowed"
    },
    {
      "name": "picture2.jpg",
      "size": 841946,
      "error": "Filetype not allowed"
    }
]}"""))
}
SkyWalker
  • 13,729
  • 18
  • 91
  • 187
  • 1
    What does `Application.uploadDo` look like? – Michael Zajac Jun 06 '14 at 17:01
  • 1
    It's going to be awfully hard to debug a function without even looking at it. – Michael Zajac Jun 08 '14 at 21:04
  • I know thank you for your interest :) I got the code in the office and now at home ... can't update till Tuesday. But I don't think it will help, it is not even executing the function due to these errors, the errors happen at Play framework level before getting into any application server-side code. – SkyWalker Jun 08 '14 at 22:42
  • **Something** is happening in the application if a result with a status has been returned. – Michael Zajac Jun 08 '14 at 22:45
  • I stuck a Debugger and nothing happens in the action function, it doesn't get there but I will post the code tomorrow for completeness. – SkyWalker Jun 09 '14 at 08:11
  • As promised, it is there now :) – SkyWalker Jun 10 '14 at 07:19

1 Answers1

2

The top answer (irritatingly unaccepted) to this question solves my problem. I'm including it here for completeness.

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)
}

@RunWith(classOf[JUnitRunner])
class AumUploadPageSpec extends PlaySpecification with FakeMultipartUpload {
  //------------------------------------------------------------------------
  "upload file correctly" in new WithApplication {
    val fileName = "idxsrs_aum_2014-06-04.csv"
    val uploadFile = new File(getClass().getResource(fileName).getPath())
    val request = FakeRequest(POST, "/aum/upload/do").withFileUpload("files[]", uploadFile, "text/csv")
    val response = route(request).get
    status(response) must equalTo(OK)
    contentType(response) must beSome.which(_ == "application/json")
  }
}
Community
  • 1
  • 1
SkyWalker
  • 13,729
  • 18
  • 91
  • 187