Last active
February 3, 2026 20:23
-
-
Save dacr/4471ee84b22d6af75bac81efd8a574e1 to your computer and use it in GitHub Desktop.
Chronos API tests / published by https://github.com/dacr/code-examples-manager #91025af9-38bd-4b7a-8b6c-7997162400fc/fdbb6ca17087b4bf74095a9a5f0c80b8ef437e2a
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env -S scala-cli test -S 3 | |
| // summary : Chronos API tests | |
| // keywords : scala, cats, sttp, chronos, test | |
| // publish : gist | |
| // authors : David Crosson | |
| // license : Apache License Version 2.0 (https://www.apache.org/licenses/LICENSE-2.0.txt) | |
| // id : 91025af9-38bd-4b7a-8b6c-7997162400fc | |
| // created-on : 2025-01-11T00:22:40+01:00 | |
| // managed-by : https://github.com/dacr/code-examples-manager | |
| // run-with : scala-cli test $file | |
| // test-with : curl http://127.0.0.1:8080/chronos | |
| // --------------------- | |
| //> using scala 3.7.0 | |
| //> using objectWrapper | |
| ////> using platform native | |
| ////> using nativeVersion 0.5.7 | |
| //> using dep com.softwaremill.sttp.client4::circe:4.0.7 | |
| //> using dep com.softwaremill.sttp.client4::cats:4.0.7 | |
| //> using dep org.typelevel::cats-effect:3.6.1 | |
| //> using dep io.circe::circe-generic:0.14.13 | |
| //> using dep org.typelevel::munit-cats-effect-3:1.0.7 | |
| // --------------------- | |
| import io.circe.generic.auto.* | |
| import io.circe.* | |
| import io.circe.parser.* | |
| import io.circe.syntax.* | |
| import sttp.client4.* | |
| import sttp.client4.circe.* | |
| import sttp.client4.httpclient.cats.HttpClientCatsBackend | |
| import HttpClientCatsBackend.resource | |
| import cats.effect.{IO, SyncIO} | |
| import munit.CatsEffectSuite | |
| import java.time.{Instant, OffsetDateTime} | |
| import java.util.UUID | |
| object Model { | |
| type CompetitorId = UUID | |
| type ChronosId = UUID | |
| // ------------------------------------------------------------------------------------------------------------------- | |
| case class ChronosSpec( | |
| name: String, | |
| description: String | |
| ) | |
| // ------------------------------------------------------------------------------------------------------------------- | |
| case class CompetitorSpec( | |
| firstName: String, | |
| lastName: String, | |
| birthDate: OffsetDateTime | |
| ) | |
| // ------------------------------------------------------------------------------------------------------------------- | |
| case class TimerSpec( | |
| name: String | |
| ) | |
| // ------------------------------------------------------------------------------------------------------------------- | |
| case class Chronos( | |
| id: ChronosId, | |
| name: String, | |
| description: String, | |
| created: Instant | |
| ) | |
| // ------------------------------------------------------------------------------------------------------------------- | |
| case class Competitor( | |
| id: CompetitorId, | |
| firstName: String, | |
| lastName: String, | |
| birthDate: OffsetDateTime | |
| ) | |
| // ------------------------------------------------------------------------------------------------------------------- | |
| enum TimingCategory { | |
| case Start | |
| case Step | |
| case End | |
| } | |
| case class Timing( | |
| category: TimingCategory, | |
| timestamp: Instant | |
| ) | |
| // ------------------------------------------------------------------------------------------------------------------- | |
| } | |
| class ClassicTests extends CatsEffectSuite { | |
| import Model.* | |
| given Encoder[TimingCategory] = Encoder[String].contramap(_.toString) | |
| given Decoder[TimingCategory] = Decoder[String].emap(s => Right(TimingCategory.valueOf(s))) // TODO manage left case | |
| val baseAPI = "http://127.0.0.1:8080/api/v1" | |
| // ----------------------------------------------------------------------------------------------- | |
| test("List chronos") { | |
| resource[IO]().use { backend => | |
| for { | |
| chronos <- | |
| quickRequest | |
| .get(uri"$baseAPI/chronos") | |
| .response(asJson[List[Chronos]]) | |
| .send(backend) | |
| } yield assertEquals(chronos.code.code, 200) | |
| } | |
| } | |
| // ----------------------------------------------------------------------------------------------- | |
| test("Create, read, update, delete a chronos") { | |
| resource[IO]().use { backend => | |
| for { | |
| randName <- IO.randomUUID.map(uuid => s"Chronos create, read, update, delete - $uuid") | |
| chronos <- | |
| quickRequest | |
| .post(uri"$baseAPI/chronos") | |
| .body(ChronosSpec(randName, s"$randName description").asJson.noSpaces) | |
| .response(asJson[Chronos]) | |
| .send(backend) | |
| .flatMap(response => IO.fromEither(response.body)) | |
| gottenChronos <- | |
| quickRequest | |
| .get(uri"$baseAPI/chronos/${chronos.id}") | |
| .response(asJson[Chronos]) | |
| .send(backend) | |
| .flatMap(response => IO.fromEither(response.body)) | |
| _ <- | |
| quickRequest | |
| .put(uri"$baseAPI/chronos/${chronos.id}") | |
| .body(ChronosSpec(randName, s"$randName updated description").asJson.noSpaces) | |
| .send(backend) | |
| updatedChronos <- | |
| quickRequest | |
| .get(uri"$baseAPI/chronos/${chronos.id}") | |
| .response(asJson[Chronos]) | |
| .send(backend) | |
| .flatMap(response => IO.fromEither(response.body)) | |
| responseDelete <- | |
| quickRequest | |
| .delete(uri"$baseAPI/chronos/${chronos.id}") | |
| .send(backend) | |
| resultAfterDelete <- | |
| quickRequest | |
| .get(uri"$baseAPI/chronos/${chronos.id}") | |
| .response(asJson[Chronos]) | |
| .send(backend) | |
| } yield { | |
| assertEquals(chronos, gottenChronos) | |
| assertEquals(updatedChronos.description, s"$randName updated description") | |
| assert(resultAfterDelete.code.code == 404) | |
| } | |
| } | |
| } | |
| // ----------------------------------------------------------------------------------------------- | |
| test("List Competitors") { | |
| resource[IO]().use { backend => | |
| for { | |
| randName <- IO.randomUUID.map(uuid => s"Competitors list - $uuid") | |
| chronos <- | |
| quickRequest | |
| .post(uri"$baseAPI/chronos") | |
| .body(ChronosSpec(randName, s"$randName description").asJson.noSpaces) | |
| .response(asJson[Chronos]) | |
| .send(backend) | |
| .flatMap(response => IO.fromEither(response.body)) | |
| competitors <- | |
| quickRequest | |
| .get(uri"$baseAPI/chronos/${chronos.id}/competitor") | |
| .response(asJson[List[Competitor]]) | |
| .send(backend) | |
| } yield assertEquals(competitors.code.code, 200) | |
| } | |
| } | |
| // ----------------------------------------------------------------------------------------------- | |
| test("Create, read, update, delete a competitor") { | |
| resource[IO]().use { backend => | |
| for { | |
| randName <- IO.randomUUID.map(uuid => s"Competitors create, read, update, delete - $uuid") | |
| randFirstName <- IO.randomUUID.map(uuid => s"Joe-$uuid") | |
| chronos <- | |
| quickRequest | |
| .post(uri"$baseAPI/chronos") | |
| .body(ChronosSpec(randName, s"$randName description").asJson.noSpaces) | |
| .response(asJson[Chronos]) | |
| .send(backend) | |
| .flatMap(response => IO.fromEither(response.body)) | |
| competitor <- | |
| quickRequest | |
| .post(uri"$baseAPI/chronos/${chronos.id}/competitor") | |
| .body(CompetitorSpec(randFirstName, "doe", OffsetDateTime.parse("1942-01-01T00:00:00Z")).asJson.noSpaces) | |
| .response(asJson[Competitor]) | |
| .send(backend) | |
| .flatMap(response => IO.fromEither(response.body)) | |
| gottenCompetitor <- | |
| quickRequest | |
| .get(uri"$baseAPI/chronos/${chronos.id}/competitor/${competitor.id}") | |
| .response(asJson[Competitor]) | |
| .send(backend) | |
| .flatMap(response => IO.fromEither(response.body)) | |
| _ <- | |
| quickRequest | |
| .put(uri"$baseAPI/chronos/${chronos.id}/competitor/${competitor.id}") | |
| .body(CompetitorSpec(s"$randFirstName-updated", competitor.lastName, competitor.birthDate).asJson.noSpaces) | |
| .send(backend) | |
| updatedCompetitor <- | |
| quickRequest | |
| .get(uri"$baseAPI/chronos/${chronos.id}/competitor/${competitor.id}") | |
| .response(asJson[Competitor]) | |
| .send(backend) | |
| .flatMap(response => IO.fromEither(response.body)) | |
| responseDelete <- | |
| quickRequest | |
| .delete(uri"$baseAPI/chronos/${chronos.id}/competitor/${competitor.id}") | |
| .send(backend) | |
| resultAfterDelete <- | |
| quickRequest | |
| .get(uri"$baseAPI/chronos/${chronos.id}/competitor/${competitor.id}") | |
| .response(asJson[Competitor]) | |
| .send(backend) | |
| } yield { | |
| assertEquals(competitor, gottenCompetitor) | |
| assert(updatedCompetitor.firstName.contains("updated")) | |
| assert(resultAfterDelete.code.code == 404) | |
| } | |
| } | |
| } | |
| // ----------------------------------------------------------------------------------------------- | |
| test("Take timings of a competitor ") { | |
| resource[IO]().use { backend => | |
| for { | |
| randName <- IO.randomUUID.map(uuid => s"Timings - $uuid") | |
| chronos <- | |
| quickRequest | |
| .post(uri"$baseAPI/chronos") | |
| .body(ChronosSpec(randName, s"$randName description").asJson.noSpaces) | |
| .response(asJson[Chronos]) | |
| .send(backend) | |
| .flatMap(response => IO.fromEither(response.body)) | |
| competitor <- | |
| quickRequest | |
| .post(uri"$baseAPI/chronos/${chronos.id}/competitor") | |
| .body(CompetitorSpec("john", "doe", OffsetDateTime.parse("1942-01-01T00:00:00Z")).asJson.noSpaces) | |
| .response(asJson[Competitor]) | |
| .send(backend) | |
| .flatMap(response => IO.fromEither(response.body)) | |
| timingStart <- | |
| quickRequest | |
| .put(uri"$baseAPI/chronos/${chronos.id}/competitor/${competitor.id}/timings?Category=${TimingCategory.Start}") | |
| .response(asJson[Timing]) | |
| .send(backend) | |
| .flatMap(response => IO.fromEither(response.body)) | |
| timingStep1 <- | |
| quickRequest | |
| .put(uri"$baseAPI/chronos/${chronos.id}/competitor/${competitor.id}/timings?Category=${TimingCategory.Step}") | |
| .response(asJson[Timing]) | |
| .send(backend) | |
| .flatMap(response => IO.fromEither(response.body)) | |
| timingStep2 <- | |
| quickRequest | |
| .put(uri"$baseAPI/chronos/${chronos.id}/competitor/${competitor.id}/timings?Category=${TimingCategory.Step}") | |
| .response(asJson[Timing]) | |
| .send(backend) | |
| .flatMap(response => IO.fromEither(response.body)) | |
| timingEnd <- | |
| quickRequest | |
| .put(uri"$baseAPI/chronos/${chronos.id}/competitor/${competitor.id}/timings?Category=${TimingCategory.End}") | |
| .response(asJson[Timing]) | |
| .send(backend) | |
| .flatMap(response => IO.fromEither(response.body)) | |
| timingEndShouldFail <- | |
| quickRequest | |
| .put(uri"$baseAPI/chronos/${chronos.id}/competitor/${competitor.id}/timings?Category=${TimingCategory.End}") | |
| .response(asJson[Timing]) | |
| .send(backend) | |
| .attempt | |
| timings <- | |
| quickRequest | |
| .get(uri"$baseAPI/chronos/${chronos.id}/competitor/${competitor.id}/timings") | |
| .response(asJson[List[Timing]]) | |
| .send(backend) | |
| .flatMap(response => IO.fromEither(response.body)) | |
| } yield { | |
| assert( | |
| timings.size == 4, | |
| timingEndShouldFail.isLeft | |
| ) | |
| } | |
| } | |
| } | |
| // ----------------------------------------------------------------------------------------------- | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment