
MediatorTest.pudu
Pudu134 lines8.3 KB
1/** @Test.Mediator.Suite — building, sending, publishing, streaming, and untyped dispatch */2module PuduLangMediator.MediatorTest34import Std.Io as Io5import Std.Test as Test6import PuduLangMediator.Context as Context7import PuduLangMediator.Mediator as Mediator8import PuduLangMediator.Message as Message9import PuduLangMediator as Messaging10import PuduLangMediator.Notification as Notification11import PuduLangMediator.Registration as Registration12import PuduLangMediator.Request as Request13import PuduLangMediator.Stream as Stream14import PuduLangMediator.Utils.Shared as Shared151617fn main() -> Int {18 let greet: Request.Kind[Str, Str, Str] = Request.kind("greet")19 let forget: Request.Kind[Str, (), Str] = Request.command("forget")20 let unknown: Request.Kind[Str, Str, Str] = Request.kind("unknown")21 let twin: Request.Kind[Str, Str, Str] = Request.kind("greet")22 let joined: Notification.Kind[Str, Str] = Notification.kind("joined")23 let joinedTwin: Notification.Kind[Str, Str] = Notification.kind("joined")24 let silent: Notification.Kind[Str, Str] = Notification.kind("silent")25 let count: Stream.Kind[Int, Int, Str] = Stream.kind("count")26 let missingStream: Stream.Kind[Int, Int, Str] = Stream.kind("missing")27 let forgotten = Shared.shared(0)28 let heard = Shared.shared(0)29 let built = Mediator.build([30 Registration.group([31 Registration.handler(&greet, |name: Str, _context: Context.Context| if name.isEmpty() { Messaging.raise("no name") } else { Ok("hello " + name) }),32 Registration.handler(&forget, fn(_name: Str, _context: Context.Context) -> Messaging.Outcome[(), Str] {33 Shared.update(&forgotten, |held: Int| held + 1)34 Ok(())35 })36 ]),37 Registration.notificationHandler(&joined, "count", fn(_name: Str, _context: Context.Context) -> Messaging.Outcome[(), Str] {38 Shared.update(&heard, |held: Int| held + 1)39 Ok(())40 }),41 Registration.streamHandler(&count, fn(upTo: Int, context: Context.Context, sink: Stream.Sink[Int]) -> Messaging.Outcome[(), Str] {42 var next = 143 while next <= upTo {44 Context.check(&context) ?45 if !sink(next) { return Ok(()) }46 next = next + 147 }48 Ok(())49 })50 ])51 let mediator = match built {52 case Ok(found) => found53 case Err(invalid) => panic(Mediator.explain(&invalid))54 }55 let refused = Mediator.build([56 Registration.handler(&greet, |name: Str, _context: Context.Context| Ok(name)),57 Registration.handler(&greet, |name: Str, _context: Context.Context| Ok(name)),58 Registration.handler(&twin, |name: Str, _context: Context.Context| Ok(name)),59 Registration.behavior(&unknown, fn(_name: Str, context: Context.Context, next: Request.Next[Str, Str]) -> Messaging.Outcome[Str, Str] { next(context) }),60 Registration.streamHandler(&Stream.kind(""), fn(_request: Int, _context: Context.Context, _sink: Stream.Sink[Int]) -> Messaging.Outcome[(), Str] { Ok(()) })61 ])62 let problems = match refused {63 case Ok(_) => []64 case Err(invalid) => invalid.problems65 }66 let explained = match refused {67 case Ok(_) => ""68 case Err(invalid) => Mediator.explain(&invalid)69 }70 let prepared = Mediator.resolve(&mediator, &greet)71 let preparedAnswer = match prepared {72 case Ok(invoke) => invoke("prepared", Context.create())73 case Err(failure) => Err(failure)74 }75 let forgot = Mediator.send(&mediator, &forget, "x")76 let dispatched = Mediator.dispatch(&mediator, Request.envelope(&greet, "envelope"))77 let unrouted = Mediator.dispatch(&mediator, Request.envelope(&unknown, "x"))78 let misrouted = Mediator.dispatch(&mediator, Request.envelope(&twin, "x"))79 let notARequest = Mediator.dispatch(&mediator, Notification.envelope(&joined, "x"))80 let published = Mediator.publish(&mediator, &joined, "ada")81 let broadcasted = Mediator.broadcast(&mediator, Notification.envelope(&joined, "grace"))82 let unheard = Mediator.broadcast(&mediator, Notification.envelope(&silent, "nobody"))83 let packedItems = Shared.shared([])84 let streamAnswer = Mediator.streamEnvelope(&mediator, Stream.envelope(&count, 3), Context.create(), fn(packed: fn() -> ()) -> Bool {85 if let Some(item) = Stream.itemOf(&count, &packed) { Shared.update(&packedItems, |held: Array[Int]| held.push(item)) }86 true87 })88 let wrongShape = Mediator.streamEnvelope(&mediator, Request.envelope(&greet, "x"), Context.create(), |_packed: fn() -> ()| true)89 let firstTwo = Shared.shared([])90 let declined = Mediator.stream(&mediator, &count, 10, fn(item: Int) -> Bool {91 Shared.update(&firstTwo, |held: Array[Int]| held.push(item))92 item < 293 })94 let stopping = Context.create()95 Context.cancel(&stopping, "enough")96 let checks = Test.suite("Mediator", &[97 Test.equals("a request answers through its handler", &Mediator.send(&mediator, &greet, "ada"), &Ok("hello ada")),98 Test.equals("a handler's error is raised", &Mediator.send(&mediator, &greet, ""), &Err(Messaging.Raised("no name"))),99 Test.equals("a command answers nothing and runs once", &(forgot, Shared.current(&forgotten)), &(Ok(()), 1)),100 Test.equals("a kind without a handler is unhandled", &Mediator.send(&mediator, &unknown, "x"), &Err(Messaging.Unhandled("unknown"))),101 Test.equals("a twin kind under a registered name is mismatched", &Mediator.send(&mediator, &twin, "x"), &Err(Messaging.Mismatched("greet"))),102 Test.equals("a resolved pipeline answers like a sent request", &preparedAnswer, &Ok("hello prepared")),103 Test.equals("resolving an unknown kind is unhandled", &(match Mediator.resolve(&mediator, &unknown) { case Ok(_) => "" case Err(failure) => Messaging.describe(&failure) }), &"No handler is registered for 'unknown'."),104 Test.equals("every problem is reported at once", &problems, &[105 "'greet' names two different kinds",106 "'greet' has 2 handlers; a request or stream takes exactly one",107 "'unknown' has behaviors or processors but no handler",108 "a kind is registered without a name"109 ]),110 Test.that("the explanation lists them", explained.startsWith("The mediator is invalid: 'greet' names two different kinds; ")),111 Test.equals("every kind is listed by shape, then registration", &Mediator.kinds(&mediator).map(|info: Message.Info| info.name), &["greet", "forget", "joined", "count"]),112 Test.equals("an envelope is answered", &(dispatched.failed, dispatched.summary, Request.answerOf(&greet, &dispatched)), &(false, "Ok(\"hello envelope\")", Some(Ok("hello envelope")))),113 Test.equals("an envelope of an unknown kind is unhandled", &Request.answerOf(&unknown, &unrouted), &Some(Err(Messaging.Unhandled("unknown")))),114 Test.equals("an envelope of a twin kind is mismatched", &Request.answerOf(&twin, &misrouted), &Some(Err(Messaging.Mismatched("greet")))),115 Test.that("a notification envelope is not a request", notARequest.failed),116 Test.equals("a publication reaches the handler", &published, &Ok(())),117 Test.equals("a broadcast reaches the handler too", &(Notification.answerOf(&joined, &broadcasted), Shared.current(&heard)), &(Some(Ok(())), 2)),118 Test.equals("a broadcast nobody hears succeeds", &(unheard.failed, Notification.answerOf(&silent, &unheard)), &(false, Some(Ok(())))),119 Test.equals("publishing a kind with no handlers succeeds", &Mediator.publish(&mediator, &silent, "x"), &Ok(())),120 Test.equals("publishing a twin kind is mismatched", &Mediator.publish(&mediator, &joinedTwin, "x"), &Err(Messaging.Mismatched("joined"))),121 Test.equals("a stream is collected", &Mediator.collect(&mediator, &count, 4), &Ok([1, 2, 3, 4])),122 Test.equals("a stream stops when the sink declines", &(declined, Shared.current(&firstTwo)), &(Ok(()), [1, 2])),123 Test.equals("a cancelled stream stops", &Mediator.streamWith(&mediator, &count, 3, stopping, |_item: Int| true), &Err(Messaging.Cancelled("cancelled: enough"))),124 Test.equals("a stream without a handler is unhandled", &Mediator.collect(&mediator, &missingStream, 1), &Err(Messaging.Unhandled("missing"))),125 Test.equals("an untyped stream hands packed items", &(Shared.current(&packedItems), Stream.answerOf(&count, &streamAnswer)), &([1, 2, 3], Some(Ok(())))),126 Test.that("a request envelope is not a stream", wrongShape.failed)127 ])128 let ran = Test.run(&checks)129 for failure in Test.failuresOf(&ran) {130 let _reported = Io.writeErrorLine(failure)131 }132 Test.report(&ran)133}134