Pudu programming language
Menu
Package

@chrismichaelps / pudu-lang-httpclient

Named HTTP clients for Pudu: a client factory, delegating handlers, pooled keep-alive connections, handler lifetimes, logging, and resilience

0.1.1Apache-2.01

InstallClose

PlaceholderLiveTest.pudu

Pudu95 lines5.3 KB

GitHub ↗
1/** @Test.Integration.PlaceholderLive.Suite — the replica checked against the public placeholder service */2module Integration.PlaceholderLiveTest34import Std.Env as Env5import Std.Io as Io6import Std.Json as Json7import Std.Result as Result8import Std.Test as Test9import PuduLangHttpClient.Client as Client10import PuduLangHttpClient.Factory.Builder as Builder11import PuduLangHttpClient.Factory as Factory12import PuduLangHttpClient.Handlers.Resilience as Resilience13import PuduLangHttpClient as HttpClient14import PuduLangHttpClient.Transport as Transport15import Support.Placeholder.Api as Api16import Support.Placeholder.Server as Placeholder1718/// The public service the replica copies.19const PUBLIC: Str = "https://jsonplaceholder.typicode.com/"2021/// How many records of each resource an API answers, in the replica's order.22fn counts(api: &Api.Api) -> Array[Int] {23  let sizes: Array[Result[Int, HttpClient.Failure]] = [24    Result.map(Api.list[Api.Post](api, "posts", &[]), |items: Array[Api.Post]| items.length()),25    Result.map(Api.list[Api.Comment](api, "comments", &[]), |items: Array[Api.Comment]| items.length()),26    Result.map(Api.list[Api.Album](api, "albums", &[]), |items: Array[Api.Album]| items.length()),27    Result.map(Api.list[Api.Photo](api, "photos", &[]), |items: Array[Api.Photo]| items.length()),28    Result.map(Api.list[Api.Todo](api, "todos", &[]), |items: Array[Api.Todo]| items.length()),29    Result.map(Api.list[Api.User](api, "users", &[]), |items: Array[Api.User]| items.length())30  ]31  sizes.map(|size: Result[Int, HttpClient.Failure]| Result.unwrapOr(size, -1))32}3334/// The owners of a sample of records: post, comment, album, photo, and todo.35fn owners(api: &Api.Api) -> Array[Int] {36  [37    Result.unwrapOr(Result.map(Api.find[Api.Post](api, "posts", 42), |p: Api.Post| p.userId), -1),38    Result.unwrapOr(Result.map(Api.find[Api.Comment](api, "comments", 42), |c: Api.Comment| c.postId), -1),39    Result.unwrapOr(Result.map(Api.find[Api.Album](api, "albums", 42), |a: Api.Album| a.userId), -1),40    Result.unwrapOr(Result.map(Api.find[Api.Photo](api, "photos", 4242), |p: Api.Photo| p.albumId), -1),41    Result.unwrapOr(Result.map(Api.find[Api.Todo](api, "todos", 142), |t: Api.Todo| t.userId), -1)42  ]43}4445/// The answers to one write of each kind: the new post's id, the replaced title, the patched title,46/// and the removal's status.47fn writes(api: &Api.Api) -> (Result[Int, HttpClient.Failure], Result[Str, HttpClient.Failure], Result[Str, HttpClient.Failure], Result[Int, HttpClient.Failure]) {48  (49    Result.map(Api.create(api, "posts", &Api.Post{userId: 1, id: 0, title: "new", body: "b"}), |p: Api.Post| p.id),50    Result.map(Api.replace(api, "posts", 1, &Api.Post{userId: 1, id: 1, title: "replaced", body: "b"}), |p: Api.Post| p.title),51    Result.map(Api.patch[Api.Post](api, "posts", 1, &Json.object(&[("title", Json.Text("patched"))])), |p: Api.Post| p.title),52    Api.remove(api, "posts", 1)53  )54}5556/// Runs the suite against the public service when `PUDU_LIVE` is `1`, and skips every check otherwise.57fn main() -> Int {58  let live = Env.variableOr("PUDU_LIVE", "") == "1"59  let reason = "set PUDU_LIVE=1 to check the replica against the public service"60  let replica = Placeholder.start()61  let local = Api.Api{client: Client.pooled(Transport.defaults()).withBaseAddress(replica.base + "/")}62  let resilient = match Resilience.standard() {63    case Ok(built) => built64    case Err(_) => panic("the standard pipeline did not build")65  }66  let factory = match Factory.build([67      Builder.named("public").withBaseAddress(PUBLIC).withTimeout(30000).withHandler(Resilience.handler(resilient)).configureTransport(|options: Transport.Options| options.withDecompression())68    ]) {69    case Ok(built) => built70    case Err(invalid) => panic(Factory.explain(&invalid))71  }72  let remote = Factory.typed(&factory, "public", |client: Client.Client| Api.Api{client: client})73  let localCounts = counts(&local)74  let localOwners = owners(&local)75  let remoteCounts = if live { counts(&remote) } else { [] }76  let remoteOwners = if live { owners(&remote) } else { [] }77  let remoteWrites = if live { Some(writes(&remote)) } else { None }78  let remoteUser: HttpClient.Outcome[Api.User] = if live { Api.find(&remote, "users", 1) } else { HttpClient.invalid("skipped") }79  let localUser: HttpClient.Outcome[Api.User] = Api.find(&local, "users", 1)80  Placeholder.stop(&replica)81  Factory.dispose(&factory)82  let checks = Test.suite("Integration.PlaceholderLive", &[83      Test.equals("the replica holds the public service's counts and owners", &(localCounts, localOwners), &([100, 500, 100, 5000, 200, 10], [5, 9, 5, 85, 8])),84      Test.skipIf(!live, reason, &Test.equals("the public service holds the replica's counts", &remoteCounts, &localCounts)),85      Test.skipIf(!live, reason, &Test.equals("records belong to the same owners in both", &remoteOwners, &localOwners)),86      Test.skipIf(!live, reason, &Test.equals("the first user is the same person in both", &Result.map(remoteUser, |u: Api.User| (u.username, u.address.geo, u.company.name)), &Result.map(localUser, |u: Api.User| (u.username, u.address.geo, u.company.name)))),87      Test.skipIf(!live, reason, &Test.equals("the public service answers every write the replica supports", &remoteWrites, &Some((Ok(101), Ok("replaced"), Ok("patched"), Ok(200)))))88    ])89  let ran = Test.run(&checks)90  for failure in Test.failuresOf(&ran) {91    let _reported = Io.writeErrorLine(failure)92  }93  Test.report(&ran)94}95