
PlaceholderContractTest.pudu
Pudu118 lines7.3 KB
1/** @Test.Integration.PlaceholderContract.Suite — the placeholder service read through a typed client */2module Integration.PlaceholderContractTest34import Std.Io as Io5import Std.Json as Json6import Std.Result as Result7import Std.Test as Test8import PuduLangHttpClient.Client as Client9import PuduLangHttpClient.Events as Events10import PuduLangHttpClient.Factory.Builder as Builder11import PuduLangHttpClient.Factory as Factory12import PuduLangHttpClient as HttpClient13import PuduLangHttpClient.Response as Response14import PuduLangHttpClient.Transport as Transport15import PuduLangHttpClient.Utils.Shared as Shared16import Support.Placeholder.Api as Api17import Support.Placeholder.Server as Placeholder181920fn sizeOf[T](outcome: &HttpClient.Outcome[Array[T]]) -> Int {21 match outcome {22 case Ok(items) => items.length()23 case Err(_) => -124 }25}262728fn idsOf(outcome: &HttpClient.Outcome[Array[Api.Post]]) -> Array[Int] {29 match outcome {30 case Ok(items) => items.map(|post: Api.Post| post.id)31 case Err(_) => []32 }33}343536fn allOwnedBy(outcome: &HttpClient.Outcome[Array[Api.Comment]], postId: Int) -> Bool {37 match outcome {38 case Ok(items) => items.filter(|comment: Api.Comment| comment.postId != postId).isEmpty() && !items.isEmpty()39 case Err(_) => false40 }41}424344fn main() -> Int {45 let service = Placeholder.start()46 let completed = Shared.shared(0)47 let listeners = Events.listeners().onRequestCompleted(|_event: Events.RequestCompleted| Shared.update(&completed, |count: Int| count + 1))48 let factory = match Factory.buildWith(&Factory.Options{listeners: listeners}, [49 Builder.defaults().withDefaultHeader("user-agent", "placeholder-suite/1.0").withTimeout(10000),50 Builder.named("placeholder").withBaseAddress(service.base + "/").withTransport(Transport.defaults().withMaxConnectionsPerServer(8).withDecompression())51 ]) {52 case Ok(built) => built53 case Err(invalid) => panic(Factory.explain(&invalid))54 }55 let api = Factory.typed(&factory, "placeholder", |client: Client.Client| Api.Api{client: client})5657 let posts: HttpClient.Outcome[Array[Api.Post]] = Api.list(&api, "posts", &[])58 let comments: HttpClient.Outcome[Array[Api.Comment]] = Api.list(&api, "comments", &[])59 let albums: HttpClient.Outcome[Array[Api.Album]] = Api.list(&api, "albums", &[])60 let photos: HttpClient.Outcome[Array[Api.Photo]] = Api.list(&api, "photos", &[])61 let todos: HttpClient.Outcome[Array[Api.Todo]] = Api.list(&api, "todos", &[])62 let users: HttpClient.Outcome[Array[Api.User]] = Api.list(&api, "users", &[])6364 let firstUser: HttpClient.Outcome[Api.User] = Api.find(&api, "users", 1)65 let post: HttpClient.Outcome[Api.Post] = Api.find(&api, "posts", 42)66 let photo: HttpClient.Outcome[Api.Photo] = Api.find(&api, "photos", 5000)67 let missing: HttpClient.Outcome[Api.Post] = Api.find(&api, "posts", 101)68 let notANumber = Result.map(Client.get(&api.client, "posts/abc"), |r: Response.Response| r.status.code)69 let unknownResource = Result.map(Client.get(&api.client, "widgets"), |r: Response.Response| r.status.code)7071 let nestedComments: HttpClient.Outcome[Array[Api.Comment]] = Api.children(&api, "posts", 7, "comments")72 let filteredComments: HttpClient.Outcome[Array[Api.Comment]] = Api.list(&api, "comments", &[("postId", "7")])73 let userPosts: HttpClient.Outcome[Array[Api.Post]] = Api.children(&api, "users", 3, "posts")74 let albumPhotos: HttpClient.Outcome[Array[Api.Photo]] = Api.children(&api, "albums", 2, "photos")75 let userAlbums: HttpClient.Outcome[Array[Api.Album]] = Api.children(&api, "users", 10, "albums")76 let userTodos: HttpClient.Outcome[Array[Api.Todo]] = Api.children(&api, "users", 4, "todos")77 let orphan: HttpClient.Outcome[Array[Api.Post]] = Api.children(&api, "users", 11, "posts")78 let doneTodos: HttpClient.Outcome[Array[Api.Todo]] = Api.list(&api, "todos", &[("userId", "1"), ("completed", "true")])7980 let page: HttpClient.Outcome[Api.Page[Api.Post]] = Api.page(&api, "posts", &[("_start", "10"), ("_limit", "5")])81 let sorted: HttpClient.Outcome[Array[Api.Post]] = Api.list(&api, "posts", &[("_sort", "id"), ("_order", "desc"), ("_limit", "3")])82 let pastEnd: HttpClient.Outcome[Array[Api.Post]] = Api.list(&api, "posts", &[("_start", "500")])83 let headers = Result.map(Client.get(&api.client, "users/1"), |r: Response.Response| Response.header(&r, "content-type"))8485 let stats = Factory.statistics(&factory, "placeholder")86 let sent = Placeholder.handled(&service)87 Factory.dispose(&factory)88 Placeholder.stop(&service)8990 let checks = Test.suite("Integration.PlaceholderContract", &[91 Test.equals("every resource holds the public number of records", &[sizeOf(&posts), sizeOf(&comments), sizeOf(&albums), sizeOf(&photos), sizeOf(&todos), sizeOf(&users)], &[100, 500, 100, 5000, 200, 10]),92 Test.equals("a user decodes with its nested address, position, and company", &Result.map(firstUser, |user: Api.User| (user.username, user.address.city, user.address.geo.lat, user.company.name)), &Ok(("Bret", "Gwenborough", "-37.3159", "Romaguera-Crona"))),93 Test.equals("a post belongs to its owner by tens", &Result.map(post, |found: Api.Post| (found.id, found.userId, found.title)), &Ok((42, 5, "post 42"))),94 Test.equals("the last photo belongs to the last album", &Result.map(photo, |found: Api.Photo| (found.albumId, found.thumbnailUrl)), &Ok((100, "https://picsum.photos/seed/5000/150"))),95 Test.equals("a missing record is not found", &missing, &Err(HttpClient.Unsuccessful(404, "Not Found"))),96 Test.equals("an id that is not a number and an unknown resource are not found", &(notANumber, unknownResource), &(Ok(404), Ok(404))),97 Test.that("a post's nested comments are its five comments", sizeOf(&nestedComments) == 5 && allOwnedBy(&nestedComments, 7)),98 Test.equals("a nested listing equals the filtered one", &nestedComments, &filteredComments),99 Test.equals("nested listings hold each owner's share", &[sizeOf(&userPosts), sizeOf(&albumPhotos), sizeOf(&userAlbums), sizeOf(&userTodos)], &[10, 50, 10, 20]),100 Test.equals("a missing parent is not found", &Result.map(orphan, |items: Array[Api.Post]| items.length()), &Err(HttpClient.Unsuccessful(404, "Not Found"))),101 Test.equals("filters combine", &Result.map(doneTodos, |items: Array[Api.Todo]| items.map(|todo: Api.Todo| todo.id)), &Ok([3, 6, 9, 12, 15, 18])),102 Test.equals("a page starts and stops where asked and reports the total", &Result.map(page, |found: Api.Page[Api.Post]| (found.items.map(|p: Api.Post| p.id), found.total)), &Ok(([11, 12, 13, 14, 15], 100))),103 Test.equals("a listing sorts in descending order", &idsOf(&sorted), &[100, 99, 98]),104 Test.equals("a start past the end lists nothing", &sizeOf(&pastEnd), &0),105 Test.equals("responses are JSON", &headers, &Ok(Some("application/json"))),106 Test.that("one pool served every request over a few kept-alive connections", match stats {107 case Some(found) => found.opened <= 2 && found.reused >= 20108 case None => false109 }),110 Test.equals("every request was heard once, and each but the unrouted one reached a route once", &(Shared.current(&completed), sent), &(24, 23))111 ])112 let ran = Test.run(&checks)113 for failure in Test.failuresOf(&ran) {114 let _reported = Io.writeErrorLine(failure)115 }116 Test.report(&ran)117}118