+htk Add akka-http-testkit as a port of spray-testkit
Currently the Specs2 support is still missing.
This commit is contained in:
parent
69888693b8
commit
af8591b5e9
8 changed files with 523 additions and 0 deletions
|
|
@ -0,0 +1,99 @@
|
|||
/*
|
||||
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package akka.http.testkit
|
||||
|
||||
import scala.collection.immutable
|
||||
import scala.concurrent.{ Await, ExecutionContext }
|
||||
import scala.concurrent.duration._
|
||||
import scala.reflect.ClassTag
|
||||
import akka.util.Timeout
|
||||
import akka.event.{ Logging, LoggingAdapter }
|
||||
import akka.http.model.parser.HeaderParser
|
||||
import akka.http.marshalling._
|
||||
import akka.http.model._
|
||||
import headers.{ HttpCredentials, RawHeader }
|
||||
import HttpMethods._
|
||||
|
||||
trait RequestBuilding extends TransformerPipelineSupport {
|
||||
type RequestTransformer = HttpRequest ⇒ HttpRequest
|
||||
|
||||
class RequestBuilder(val method: HttpMethod) {
|
||||
def apply(): HttpRequest =
|
||||
apply("/")
|
||||
|
||||
def apply(uri: String): HttpRequest =
|
||||
apply(uri, HttpEntity.Empty)
|
||||
|
||||
def apply[T](uri: String, content: T)(implicit m: ToEntityMarshallers[T], ec: ExecutionContext): HttpRequest =
|
||||
apply(uri, Some(content))
|
||||
|
||||
def apply[T](uri: String, content: Option[T])(implicit m: ToEntityMarshallers[T], ec: ExecutionContext): HttpRequest =
|
||||
apply(Uri(uri), content)
|
||||
|
||||
def apply(uri: String, entity: HttpEntity.Regular): HttpRequest =
|
||||
apply(Uri(uri), entity)
|
||||
|
||||
def apply(uri: Uri): HttpRequest =
|
||||
apply(uri, HttpEntity.Empty)
|
||||
|
||||
def apply[T](uri: Uri, content: T)(implicit m: ToEntityMarshallers[T], ec: ExecutionContext): HttpRequest =
|
||||
apply(uri, Some(content))
|
||||
|
||||
def apply[T](uri: Uri, content: Option[T])(implicit m: ToEntityMarshallers[T], timeout: Timeout = Timeout(1.second), ec: ExecutionContext): HttpRequest =
|
||||
content match {
|
||||
case None ⇒ apply(uri, HttpEntity.Empty)
|
||||
case Some(value) ⇒
|
||||
val entity = Marshal(value).to[HttpEntity.Regular].await(timeout.duration)
|
||||
apply(uri, entity)
|
||||
}
|
||||
|
||||
def apply(uri: Uri, entity: HttpEntity.Regular): HttpRequest =
|
||||
HttpRequest(method, uri, Nil, entity)
|
||||
}
|
||||
|
||||
val Get = new RequestBuilder(GET)
|
||||
val Post = new RequestBuilder(POST)
|
||||
val Put = new RequestBuilder(PUT)
|
||||
val Patch = new RequestBuilder(PATCH)
|
||||
val Delete = new RequestBuilder(DELETE)
|
||||
val Options = new RequestBuilder(OPTIONS)
|
||||
val Head = new RequestBuilder(HEAD)
|
||||
|
||||
// TODO: reactivate after HTTP message encoding has been ported
|
||||
//def encode(encoder: Encoder, flow: FlowMaterializer): RequestTransformer = encoder.encode(_, flow)
|
||||
|
||||
def addHeader(header: HttpHeader): RequestTransformer = _.mapHeaders(header +: _)
|
||||
|
||||
def addHeader(headerName: String, headerValue: String): RequestTransformer = {
|
||||
val rawHeader = RawHeader(headerName, headerValue)
|
||||
addHeader(HeaderParser.parseHeader(rawHeader).left.flatMap(_ ⇒ Right(rawHeader)).right.get)
|
||||
}
|
||||
|
||||
def addHeaders(first: HttpHeader, more: HttpHeader*): RequestTransformer = _.mapHeaders(_ ++ (first +: more))
|
||||
|
||||
def mapHeaders(f: immutable.Seq[HttpHeader] ⇒ immutable.Seq[HttpHeader]): RequestTransformer = _.mapHeaders(f)
|
||||
|
||||
def removeHeader(headerName: String): RequestTransformer =
|
||||
_ mapHeaders (_ filterNot (_.name equalsIgnoreCase headerName))
|
||||
|
||||
def removeHeader[T <: HttpHeader: ClassTag]: RequestTransformer =
|
||||
removeHeader(implicitly[ClassTag[T]].runtimeClass)
|
||||
|
||||
def removeHeader(clazz: Class[_]): RequestTransformer =
|
||||
_ mapHeaders (_ filterNot clazz.isInstance)
|
||||
|
||||
def removeHeaders(names: String*): RequestTransformer =
|
||||
_ mapHeaders (_ filterNot (header ⇒ names exists (_ equalsIgnoreCase header.name)))
|
||||
|
||||
def addCredentials(credentials: HttpCredentials) = addHeader(headers.Authorization(credentials))
|
||||
|
||||
def logRequest(log: LoggingAdapter, level: Logging.LogLevel = Logging.DebugLevel) = logValue[HttpRequest](log, level)
|
||||
|
||||
def logRequest(logFun: HttpRequest ⇒ Unit) = logValue[HttpRequest](logFun)
|
||||
|
||||
implicit def header2AddHeader(header: HttpHeader): RequestTransformer = addHeader(header)
|
||||
}
|
||||
|
||||
object RequestBuilding extends RequestBuilding
|
||||
|
|
@ -0,0 +1,144 @@
|
|||
/*
|
||||
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package akka.http.testkit
|
||||
|
||||
import akka.http.util.Deferrable
|
||||
import com.typesafe.config.{ ConfigFactory, Config }
|
||||
import scala.collection.immutable
|
||||
import scala.concurrent.duration._
|
||||
import scala.util.DynamicVariable
|
||||
import scala.reflect.ClassTag
|
||||
import org.scalatest.Suite
|
||||
import akka.actor.ActorSystem
|
||||
import akka.stream.FlowMaterializer
|
||||
import akka.http.routing._
|
||||
import akka.http.unmarshalling._
|
||||
import akka.http.model._
|
||||
import headers.Host
|
||||
|
||||
trait RouteTest extends RequestBuilding with RouteTestResultComponent {
|
||||
this: TestFrameworkInterface ⇒
|
||||
|
||||
/** Override to supply a custom ActorSystem */
|
||||
protected def createActorSystem(): ActorSystem =
|
||||
ActorSystem(actorSystemNameFrom(getClass), testConfig)
|
||||
|
||||
def actorSystemNameFrom(clazz: Class[_]) =
|
||||
clazz.getName
|
||||
.replace('.', '-')
|
||||
.replace('_', '-')
|
||||
.filter(_ != '$')
|
||||
|
||||
def testConfigSource: String = ""
|
||||
def testConfig: Config = {
|
||||
val source = testConfigSource
|
||||
val config = if (source.isEmpty) ConfigFactory.empty() else ConfigFactory.parseString(source)
|
||||
config.withFallback(ConfigFactory.load())
|
||||
}
|
||||
implicit val system = createActorSystem()
|
||||
implicit def executor = system.dispatcher
|
||||
implicit val materializer = FlowMaterializer()
|
||||
|
||||
def cleanUp(): Unit = system.shutdown()
|
||||
|
||||
private val dynRR = new DynamicVariable[RouteTestResult](null)
|
||||
private def result =
|
||||
if (dynRR.value ne null) dynRR.value
|
||||
else sys.error("This value is only available inside of a `check` construct!")
|
||||
|
||||
def check[T](body: ⇒ T): RouteTestResult ⇒ T = result ⇒ dynRR.withValue(result.awaitResult)(body)
|
||||
|
||||
def handled: Boolean = result.handled
|
||||
def response: HttpResponse = result.response
|
||||
def entity: HttpEntity = result.entity
|
||||
def chunks: immutable.Seq[HttpEntity.ChunkStreamPart] = result.chunks
|
||||
def entityAs[T: FromEntityUnmarshaller: ClassTag](implicit timeout: Duration = 1.second): T = {
|
||||
def msg(e: Throwable) = s"Could not unmarshal entity to type '${implicitly[ClassTag[T]]}' for `entityAs` assertion: $e\n\nResponse was: $response"
|
||||
Unmarshal(entity).to[T].recover[T] { case error ⇒ failTest(msg(error)) }.await(timeout)
|
||||
}
|
||||
def responseAs[T: FromResponseUnmarshaller: ClassTag](implicit timeout: Duration = 1.second): T = {
|
||||
def msg(e: Throwable) = s"Could not unmarshal response to type '${implicitly[ClassTag[T]]}' for `responseAs` assertion: $e\n\nResponse was: $response"
|
||||
Unmarshal(response).to[T].recover[T] { case error ⇒ failTest(msg(error)) }.await(timeout)
|
||||
}
|
||||
def contentType: ContentType = entity.contentType
|
||||
def mediaType: MediaType = contentType.mediaType
|
||||
def charset: HttpCharset = contentType.charset
|
||||
def definedCharset: Option[HttpCharset] = contentType.definedCharset
|
||||
def headers: immutable.Seq[HttpHeader] = response.headers
|
||||
def header[T <: HttpHeader: ClassTag]: Option[T] = response.header[T]
|
||||
def header(name: String): Option[HttpHeader] = response.headers.find(_.is(name.toLowerCase))
|
||||
def status: StatusCode = response.status
|
||||
|
||||
def closingExtension: String = chunks.lastOption match {
|
||||
case Some(HttpEntity.LastChunk(extension, _)) ⇒ extension
|
||||
case _ ⇒ ""
|
||||
}
|
||||
def trailer: immutable.Seq[HttpHeader] = chunks.lastOption match {
|
||||
case Some(HttpEntity.LastChunk(_, trailer)) ⇒ trailer
|
||||
case _ ⇒ Nil
|
||||
}
|
||||
|
||||
def rejections: List[Rejection] = result.rejections
|
||||
def rejection: Rejection = {
|
||||
val r = rejections
|
||||
if (r.size == 1) r.head else failTest("Expected a single rejection but got %s (%s)".format(r.size, r))
|
||||
}
|
||||
|
||||
/**
|
||||
* A dummy that can be used as `~> runRoute` to run the route but without blocking for the result.
|
||||
* The result of the pipeline is the result that can later be checked with `check`. See the
|
||||
* "separate running route from checking" example from ScalatestRouteTestSpec.scala.
|
||||
*/
|
||||
def runRoute: RouteTestResult ⇒ RouteTestResult = akka.http.util.identityFunc
|
||||
|
||||
// there is already an implicit class WithTransformation in scope (inherited from akka.http.testkit.TransformerPipelineSupport)
|
||||
// however, this one takes precedence
|
||||
implicit class WithTransformation2(request: HttpRequest) {
|
||||
def ~>[A, B](f: A ⇒ B)(implicit ta: TildeArrow[A, B]): ta.Out = ta(request, f)
|
||||
}
|
||||
|
||||
abstract class TildeArrow[A, B] {
|
||||
type Out
|
||||
def apply(request: HttpRequest, f: A ⇒ B): Out
|
||||
}
|
||||
|
||||
case class DefaultHostInfo(host: Host, securedConnection: Boolean)
|
||||
object DefaultHostInfo {
|
||||
implicit def defaultHost: DefaultHostInfo = DefaultHostInfo(Host("example.com"), securedConnection = false)
|
||||
}
|
||||
object TildeArrow {
|
||||
implicit object InjectIntoRequestTransformer extends TildeArrow[HttpRequest, HttpRequest] {
|
||||
type Out = HttpRequest
|
||||
def apply(request: HttpRequest, f: HttpRequest ⇒ HttpRequest) = f(request)
|
||||
}
|
||||
implicit def injectIntoRoute(implicit timeout: RouteTestTimeout, setup: RoutingSetup,
|
||||
defaultHostInfo: DefaultHostInfo) =
|
||||
new TildeArrow[RequestContext, Deferrable[RouteResult]] {
|
||||
type Out = RouteTestResult
|
||||
def apply(request: HttpRequest, route: Route): Out = {
|
||||
val routeTestResult = new RouteTestResult(timeout.duration)(setup.materializer)
|
||||
val effectiveRequest =
|
||||
request.withEffectiveUri(
|
||||
securedConnection = defaultHostInfo.securedConnection,
|
||||
defaultHostHeader = defaultHostInfo.host)
|
||||
val ctx = new RequestContextImpl(effectiveRequest, setup.routingLog.requestLog(effectiveRequest))
|
||||
val sealedExceptionHandler = {
|
||||
import setup._
|
||||
if (exceptionHandler.isDefault) exceptionHandler
|
||||
else exceptionHandler orElse ExceptionHandler.default(settings)(setup.executionContext)
|
||||
}
|
||||
val semiSealedRoute = // sealed for exceptions but not for rejections
|
||||
Directives.handleExceptions(sealedExceptionHandler) { route }
|
||||
val deferrableRouteResult = semiSealedRoute(ctx)
|
||||
deferrableRouteResult.foreach(routeTestResult.handleResult)(setup.executor)
|
||||
routeTestResult
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
trait ScalatestRouteTest extends RouteTest with TestFrameworkInterface.Scalatest { this: Suite ⇒ }
|
||||
|
||||
//FIXME: trait Specs2RouteTest extends RouteTest with Specs2Interface
|
||||
|
|
@ -0,0 +1,102 @@
|
|||
/*
|
||||
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package akka.http.testkit
|
||||
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import org.reactivestreams.Publisher
|
||||
import scala.collection.immutable
|
||||
import scala.concurrent.duration._
|
||||
import scala.concurrent.{ Await, ExecutionContext }
|
||||
import akka.stream.FlowMaterializer
|
||||
import akka.stream.scaladsl.Flow
|
||||
import akka.http.model.HttpEntity.ChunkStreamPart
|
||||
import akka.http.routing._
|
||||
import akka.http.model._
|
||||
|
||||
trait RouteTestResultComponent {
|
||||
|
||||
def failTest(msg: String): Nothing
|
||||
|
||||
/**
|
||||
* A receptacle for the response or rejections created by a route.
|
||||
*/
|
||||
class RouteTestResult(timeout: FiniteDuration)(implicit fm: FlowMaterializer) {
|
||||
private[this] var result: Option[Either[List[Rejection], HttpResponse]] = None
|
||||
private[this] val latch = new CountDownLatch(1)
|
||||
|
||||
def handled: Boolean = synchronized { result.isDefined && result.get.isRight }
|
||||
|
||||
def rejections: List[Rejection] = synchronized {
|
||||
result match {
|
||||
case Some(Left(rejections)) ⇒ rejections
|
||||
case Some(Right(response)) ⇒ failTest("Request was not rejected, response was " + response)
|
||||
case None ⇒ failNeitherCompletedNorRejected()
|
||||
}
|
||||
}
|
||||
|
||||
def response: HttpResponse = rawResponse.copy(entity = entity)
|
||||
|
||||
/** Returns a "fresh" entity with a "fresh" unconsumed byte- or chunk stream (if not strict) */
|
||||
def entity: HttpEntity = entityRecreator()
|
||||
|
||||
def chunks: immutable.Seq[ChunkStreamPart] =
|
||||
entity match {
|
||||
case HttpEntity.Chunked(_, chunks) ⇒ awaitAllElements[ChunkStreamPart](chunks)
|
||||
case _ ⇒ Nil
|
||||
}
|
||||
|
||||
def ~>[T](f: RouteTestResult ⇒ T): T = f(this)
|
||||
|
||||
private def rawResponse: HttpResponse = synchronized {
|
||||
result match {
|
||||
case Some(Right(response)) ⇒ response
|
||||
case Some(Left(Nil)) ⇒ failTest("Request was rejected")
|
||||
case Some(Left(rejection :: Nil)) ⇒ failTest("Request was rejected with rejection " + rejection)
|
||||
case Some(Left(rejections)) ⇒ failTest("Request was rejected with rejections " + rejections)
|
||||
case None ⇒ failNeitherCompletedNorRejected()
|
||||
}
|
||||
}
|
||||
|
||||
private[testkit] def handleResult(rr: RouteResult)(implicit ec: ExecutionContext): Unit =
|
||||
synchronized {
|
||||
if (result.isEmpty) {
|
||||
result = rr match {
|
||||
case RouteResult.Complete(response) ⇒ Some(Right(response))
|
||||
case RouteResult.Rejected(rejections) ⇒ Some(Left(RejectionHandler.applyTransformations(rejections)))
|
||||
case RouteResult.Failure(error) ⇒ sys.error("Route produced exception: " + error)
|
||||
}
|
||||
latch.countDown()
|
||||
} else failTest("Route completed/rejected more than once")
|
||||
}
|
||||
|
||||
private[testkit] def awaitResult: this.type = {
|
||||
latch.await(timeout.toMillis, MILLISECONDS)
|
||||
this
|
||||
}
|
||||
|
||||
private[this] lazy val entityRecreator: () ⇒ HttpEntity =
|
||||
rawResponse.entity match {
|
||||
case s: HttpEntity.Strict ⇒ () ⇒ s
|
||||
|
||||
case HttpEntity.Default(contentType, contentLength, data) ⇒
|
||||
val dataChunks = awaitAllElements(data);
|
||||
{ () ⇒ HttpEntity.Default(contentType, contentLength, Flow(dataChunks).toPublisher()) }
|
||||
|
||||
case HttpEntity.CloseDelimited(contentType, data) ⇒
|
||||
val dataChunks = awaitAllElements(data);
|
||||
{ () ⇒ HttpEntity.CloseDelimited(contentType, Flow(dataChunks).toPublisher()) }
|
||||
|
||||
case HttpEntity.Chunked(contentType, chunks) ⇒
|
||||
val dataChunks = awaitAllElements(chunks);
|
||||
{ () ⇒ HttpEntity.Chunked(contentType, Flow(dataChunks).toPublisher()) }
|
||||
}
|
||||
|
||||
private def failNeitherCompletedNorRejected(): Nothing =
|
||||
failTest("Request was neither completed nor rejected within " + timeout)
|
||||
|
||||
private def awaitAllElements[T](data: Publisher[T]): immutable.Seq[T] =
|
||||
Await.result(Flow(data).grouped(Int.MaxValue).toFuture(), timeout)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
/*
|
||||
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package akka.http.testkit
|
||||
|
||||
import scala.concurrent.duration._
|
||||
import akka.actor.ActorSystem
|
||||
import akka.testkit._
|
||||
|
||||
case class RouteTestTimeout(duration: FiniteDuration)
|
||||
|
||||
object RouteTestTimeout {
|
||||
implicit def default(implicit system: ActorSystem) = RouteTestTimeout(1.second dilated)
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package akka.http.testkit
|
||||
|
||||
import org.scalatest.exceptions.TestFailedException
|
||||
import org.scalatest.{ BeforeAndAfterAll, Suite }
|
||||
|
||||
trait TestFrameworkInterface {
|
||||
|
||||
def cleanUp()
|
||||
|
||||
def failTest(msg: String): Nothing
|
||||
}
|
||||
|
||||
object TestFrameworkInterface {
|
||||
|
||||
trait Scalatest extends TestFrameworkInterface with BeforeAndAfterAll {
|
||||
this: Suite ⇒
|
||||
|
||||
def failTest(msg: String) = throw new TestFailedException(msg, 11)
|
||||
|
||||
abstract override protected def afterAll(): Unit = {
|
||||
cleanUp()
|
||||
super.afterAll()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package akka.http.testkit
|
||||
|
||||
import akka.event.{ Logging, LoggingAdapter }
|
||||
|
||||
import scala.concurrent.{ Future, ExecutionContext }
|
||||
|
||||
trait TransformerPipelineSupport {
|
||||
|
||||
def logValue[T](log: LoggingAdapter, level: Logging.LogLevel = Logging.DebugLevel): T ⇒ T =
|
||||
logValue { value ⇒ log.log(level, value.toString) }
|
||||
|
||||
def logValue[T](logFun: T ⇒ Unit): T ⇒ T = { response ⇒
|
||||
logFun(response)
|
||||
response
|
||||
}
|
||||
|
||||
implicit class WithTransformation[A](value: A) {
|
||||
def ~>[B](f: A ⇒ B): B = f(value)
|
||||
}
|
||||
|
||||
implicit class WithTransformerConcatenation[A, B](f: A ⇒ B) extends (A ⇒ B) {
|
||||
def apply(input: A) = f(input)
|
||||
def ~>[AA, BB, R](g: AA ⇒ BB)(implicit aux: TransformerAux[A, B, AA, BB, R]) =
|
||||
new WithTransformerConcatenation[A, R](aux(f, g))
|
||||
}
|
||||
}
|
||||
|
||||
object TransformerPipelineSupport extends TransformerPipelineSupport
|
||||
|
||||
trait TransformerAux[A, B, AA, BB, R] {
|
||||
def apply(f: A ⇒ B, g: AA ⇒ BB): A ⇒ R
|
||||
}
|
||||
|
||||
object TransformerAux {
|
||||
implicit def aux1[A, B, C] = new TransformerAux[A, B, B, C, C] {
|
||||
def apply(f: A ⇒ B, g: B ⇒ C): A ⇒ C = f andThen g
|
||||
}
|
||||
implicit def aux2[A, B, C](implicit ec: ExecutionContext) =
|
||||
new TransformerAux[A, Future[B], B, C, Future[C]] {
|
||||
def apply(f: A ⇒ Future[B], g: B ⇒ C): A ⇒ Future[C] = f(_).map(g)
|
||||
}
|
||||
implicit def aux3[A, B, C](implicit ec: ExecutionContext) =
|
||||
new TransformerAux[A, Future[B], B, Future[C], Future[C]] {
|
||||
def apply(f: A ⇒ Future[B], g: B ⇒ Future[C]): A ⇒ Future[C] = f(_).flatMap(g)
|
||||
}
|
||||
}
|
||||
2
akka-http-testkit/src/test/resources/reference.conf
Normal file
2
akka-http-testkit/src/test/resources/reference.conf
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
# override strange reference.conf setting in akka-stream test scope
|
||||
akka.actor.default-mailbox.mailbox-type = akka.dispatch.UnboundedMailbox
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
/*
|
||||
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package akka.http.testkit
|
||||
|
||||
import scala.concurrent.duration._
|
||||
import org.scalatest.FreeSpec
|
||||
import org.scalatest.Matchers
|
||||
import akka.testkit.TestProbe
|
||||
import akka.util.Timeout
|
||||
import akka.pattern.ask
|
||||
import akka.http.model.headers.RawHeader
|
||||
import akka.http.routing._
|
||||
import akka.http.model._
|
||||
import StatusCodes._
|
||||
import HttpMethods._
|
||||
import ScalaRoutingDSL._
|
||||
|
||||
class ScalatestRouteTestSpec extends FreeSpec with Matchers with ScalatestRouteTest {
|
||||
|
||||
"The ScalatestRouteTest should support" - {
|
||||
|
||||
"the most simple and direct route test" in {
|
||||
Get() ~> complete(HttpResponse()) ~> { rr ⇒ rr.awaitResult; rr.response } shouldEqual HttpResponse()
|
||||
}
|
||||
|
||||
"a test using a directive and some checks" in {
|
||||
val pinkHeader = RawHeader("Fancy", "pink")
|
||||
Get() ~> addHeader(pinkHeader) ~> {
|
||||
respondWithHeader(pinkHeader) {
|
||||
complete("abc")
|
||||
}
|
||||
} ~> check {
|
||||
status shouldEqual OK
|
||||
entity shouldEqual HttpEntity(ContentTypes.`text/plain(UTF-8)`, "abc")
|
||||
header("Fancy") shouldEqual Some(pinkHeader)
|
||||
}
|
||||
}
|
||||
|
||||
"proper rejection collection" in {
|
||||
Post("/abc", "content") ~> {
|
||||
(get | put) {
|
||||
complete("naah")
|
||||
}
|
||||
} ~> check {
|
||||
rejections shouldEqual List(MethodRejection(GET), MethodRejection(PUT))
|
||||
}
|
||||
}
|
||||
|
||||
"separation of route execution from checking" in {
|
||||
val pinkHeader = RawHeader("Fancy", "pink")
|
||||
|
||||
case object Command
|
||||
val service = TestProbe()
|
||||
val handler = TestProbe()
|
||||
implicit def serviceRef = service.ref
|
||||
implicit val askTimeout: Timeout = 1.second
|
||||
|
||||
val result =
|
||||
Get() ~> pinkHeader ~> {
|
||||
respondWithHeader(pinkHeader) {
|
||||
complete(handler.ref.ask(Command).mapTo[String])
|
||||
}
|
||||
} ~> runRoute
|
||||
|
||||
handler.expectMsg(Command)
|
||||
handler.reply("abc")
|
||||
|
||||
check {
|
||||
status shouldEqual OK
|
||||
entity shouldEqual HttpEntity(ContentTypes.`text/plain(UTF-8)`, "abc")
|
||||
header("Fancy") shouldEqual Some(pinkHeader)
|
||||
}(result)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: remove once RespondWithDirectives have been ported
|
||||
def respondWithHeader(responseHeader: HttpHeader): Directive0 =
|
||||
mapHttpResponseHeaders(responseHeader +: _)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue