Skip to content

Commit

Permalink
Merge pull request #1257 from disneystreaming/confirm-empty-body-fixes
Browse files Browse the repository at this point in the history
Add Tests to Confirm Empty Body Fixes
  • Loading branch information
daddykotex authored Oct 11, 2023
2 parents 410478e + 53a3cba commit ce93496
Show file tree
Hide file tree
Showing 6 changed files with 149 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,24 @@
}
}
},
"/head-request": {
"head": {
"operationId": "HeadRequest",
"responses": {
"200": {
"description": "HeadRequest 200 response",
"headers": {
"Test": {
"schema": {
"type": "string"
},
"required": true
}
}
}
}
}
},
"/headers/": {
"post": {
"operationId": "HeaderEndpoint",
Expand Down Expand Up @@ -283,6 +301,16 @@
}
}
},
"/no-content": {
"get": {
"operationId": "NoContentRequest",
"responses": {
"204": {
"description": "NoContentRequest 204 response"
}
}
}
},
"/optional-output": {
"get": {
"operationId": "OptionalOutput",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package smithy4s.example

import smithy4s.Hints
import smithy4s.Schema
import smithy4s.ShapeId
import smithy4s.ShapeTag
import smithy4s.schema.Schema.string
import smithy4s.schema.Schema.struct

final case class HeadRequestOutput(test: String)

object HeadRequestOutput extends ShapeTag.Companion[HeadRequestOutput] {
val id: ShapeId = ShapeId("smithy4s.example", "HeadRequestOutput")

val hints: Hints = Hints.empty

implicit val schema: Schema[HeadRequestOutput] = struct(
string.required[HeadRequestOutput]("test", _.test).addHints(smithy.api.HttpHeader("Test")),
){
HeadRequestOutput.apply
}.withId(id).addHints(hints)
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ trait PizzaAdminServiceGen[F[_, _, _, _, _]] {
def reservation(name: String, town: Option[String] = None): F[ReservationInput, Nothing, ReservationOutput, Nothing, Nothing]
def echo(pathParam: String, body: EchoBody, queryParam: Option[String] = None): F[EchoInput, Nothing, Unit, Nothing, Nothing]
def optionalOutput(): F[Unit, Nothing, OptionalOutputOutput, Nothing, Nothing]
def headRequest(): F[Unit, Nothing, HeadRequestOutput, Nothing, Nothing]
def noContentRequest(): F[Unit, Nothing, Unit, Nothing, Nothing]

def transform: Transformation.PartiallyApplied[PizzaAdminServiceGen[F]] = Transformation.of[PizzaAdminServiceGen[F]](this)
}
Expand Down Expand Up @@ -62,6 +64,8 @@ object PizzaAdminServiceGen extends Service.Mixin[PizzaAdminServiceGen, PizzaAdm
PizzaAdminServiceOperation.Reservation,
PizzaAdminServiceOperation.Echo,
PizzaAdminServiceOperation.OptionalOutput,
PizzaAdminServiceOperation.HeadRequest,
PizzaAdminServiceOperation.NoContentRequest,
)

def input[I, E, O, SI, SO](op: PizzaAdminServiceOperation[I, E, O, SI, SO]): I = op.input
Expand Down Expand Up @@ -110,6 +114,8 @@ object PizzaAdminServiceOperation {
def reservation(name: String, town: Option[String] = None) = Reservation(ReservationInput(name, town))
def echo(pathParam: String, body: EchoBody, queryParam: Option[String] = None) = Echo(EchoInput(pathParam, body, queryParam))
def optionalOutput() = OptionalOutput()
def headRequest() = HeadRequest()
def noContentRequest() = NoContentRequest()
}
class Transformed[P[_, _, _, _, _], P1[_ ,_ ,_ ,_ ,_]](alg: PizzaAdminServiceGen[P], f: PolyFunction5[P, P1]) extends PizzaAdminServiceGen[P1] {
def addMenuItem(restaurant: String, menuItem: MenuItem) = f[AddMenuItemRequest, PizzaAdminServiceOperation.AddMenuItemError, AddMenuItemResult, Nothing, Nothing](alg.addMenuItem(restaurant, menuItem))
Expand All @@ -124,6 +130,8 @@ object PizzaAdminServiceOperation {
def reservation(name: String, town: Option[String] = None) = f[ReservationInput, Nothing, ReservationOutput, Nothing, Nothing](alg.reservation(name, town))
def echo(pathParam: String, body: EchoBody, queryParam: Option[String] = None) = f[EchoInput, Nothing, Unit, Nothing, Nothing](alg.echo(pathParam, body, queryParam))
def optionalOutput() = f[Unit, Nothing, OptionalOutputOutput, Nothing, Nothing](alg.optionalOutput())
def headRequest() = f[Unit, Nothing, HeadRequestOutput, Nothing, Nothing](alg.headRequest())
def noContentRequest() = f[Unit, Nothing, Unit, Nothing, Nothing](alg.noContentRequest())
}

def toPolyFunction[P[_, _, _, _, _]](impl: PizzaAdminServiceGen[P]): PolyFunction5[PizzaAdminServiceOperation, P] = new PolyFunction5[PizzaAdminServiceOperation, P] {
Expand Down Expand Up @@ -663,5 +671,31 @@ object PizzaAdminServiceOperation {
.withHints(smithy.api.Http(method = smithy.api.NonEmptyString("GET"), uri = smithy.api.NonEmptyString("/optional-output"), code = 200), smithy.api.Readonly())
def wrap(input: Unit) = OptionalOutput()
}
final case class HeadRequest() extends PizzaAdminServiceOperation[Unit, Nothing, HeadRequestOutput, Nothing, Nothing] {
def run[F[_, _, _, _, _]](impl: PizzaAdminServiceGen[F]): F[Unit, Nothing, HeadRequestOutput, Nothing, Nothing] = impl.headRequest()
def ordinal = 12
def input: Unit = ()
def endpoint: smithy4s.Endpoint[PizzaAdminServiceOperation,Unit, Nothing, HeadRequestOutput, Nothing, Nothing] = HeadRequest
}
object HeadRequest extends smithy4s.Endpoint[PizzaAdminServiceOperation,Unit, Nothing, HeadRequestOutput, Nothing, Nothing] {
val schema: OperationSchema[Unit, Nothing, HeadRequestOutput, Nothing, Nothing] = Schema.operation(ShapeId("smithy4s.example", "HeadRequest"))
.withInput(unit.addHints(smithy4s.internals.InputOutput.Input.widen))
.withOutput(HeadRequestOutput.schema.addHints(smithy4s.internals.InputOutput.Output.widen))
.withHints(smithy.api.Http(method = smithy.api.NonEmptyString("HEAD"), uri = smithy.api.NonEmptyString("/head-request"), code = 200), smithy.api.Readonly())
def wrap(input: Unit) = HeadRequest()
}
final case class NoContentRequest() extends PizzaAdminServiceOperation[Unit, Nothing, Unit, Nothing, Nothing] {
def run[F[_, _, _, _, _]](impl: PizzaAdminServiceGen[F]): F[Unit, Nothing, Unit, Nothing, Nothing] = impl.noContentRequest()
def ordinal = 13
def input: Unit = ()
def endpoint: smithy4s.Endpoint[PizzaAdminServiceOperation,Unit, Nothing, Unit, Nothing, Nothing] = NoContentRequest
}
object NoContentRequest extends smithy4s.Endpoint[PizzaAdminServiceOperation,Unit, Nothing, Unit, Nothing, Nothing] {
val schema: OperationSchema[Unit, Nothing, Unit, Nothing, Nothing] = Schema.operation(ShapeId("smithy4s.example", "NoContentRequest"))
.withInput(unit.addHints(smithy4s.internals.InputOutput.Input.widen))
.withOutput(unit.addHints(smithy4s.internals.InputOutput.Output.widen))
.withHints(smithy.api.Http(method = smithy.api.NonEmptyString("GET"), uri = smithy.api.NonEmptyString("/no-content"), code = 204), smithy.api.Readonly())
def wrap(input: Unit) = NoContentRequest()
}
}

6 changes: 6 additions & 0 deletions modules/tests/src/smithy4s/tests/PizzaAdminServiceImpl.scala
Original file line number Diff line number Diff line change
Expand Up @@ -120,4 +120,10 @@ class PizzaAdminServiceImpl(ref: Ref[IO, State]) extends PizzaAdminService[IO] {
def optionalOutput(): IO[OptionalOutputOutput] =
IO.pure(OptionalOutputOutput(None))

def headRequest(): cats.effect.IO[HeadRequestOutput] =
IO.pure(HeadRequestOutput("test"))

def noContentRequest(): cats.effect.IO[Unit] =
IO.pure(())

}
41 changes: 41 additions & 0 deletions modules/tests/src/smithy4s/tests/PizzaSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,47 @@ abstract class PizzaSpec
expect(matchResult == None)
}

routerTest("Response to a HEAD request should have empty body") {
(client, uri, log) =>
for {
res <- client.send[String](
HEAD((uri / "head-request")),
log
)
} yield {
val (code, headers, body) = res
// There may be other headers, but this one should definitely exist.
// In general, content-length and content-type headers should be omitted
// but we won't fail the test if they aren't since the HTTP Spec is
// fairly vague and thus permissive in this area.
val expectedHeaders = Map(
"Test" -> List("test")
)
val containsAllExpectedHeaders =
expectedHeaders.forall(h => headers.get(h._1).contains(h._2))
expect.same(code, 200) &&
expect.same(body, "") &&
expect(
containsAllExpectedHeaders,
s"Expected to find all of $expectedHeaders inside of $headers"
)
}
}

routerTest("204 no content response should have empty body") {
(client, uri, log) =>
for {
res <- client.send[String](
GET((uri / "no-content")),
log
)
} yield {
val (code, _, body) = res
expect.same(code, 204) &&
expect.same(body, "")
}
}

type Res = (Client[IO], Uri)
def sharedResource: Resource[IO, (Client[IO], Uri)] = for {
stateRef <- Resource.eval(
Expand Down
18 changes: 18 additions & 0 deletions sampleSpecs/pizza.smithy
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ service PizzaAdminService {
Reservation
Echo
OptionalOutput
HeadRequest,
NoContentRequest
]
}

Expand Down Expand Up @@ -364,3 +366,19 @@ operation OptionalOutput {
body: String
}
}

@http(method: "HEAD", uri: "/head-request")
@readonly
operation HeadRequest {
output: HeadRequestOutput
}

structure HeadRequestOutput {
@httpHeader("Test")
@required
test: String
}

@http(method: "GET", uri: "/no-content", code: 204)
@readonly
operation NoContentRequest {}

0 comments on commit ce93496

Please sign in to comment.