Pudu programming language
Menu
Package

@chrismichaelps / pudu-lang-mediator

In-process messaging for Pudu: requests, notifications, streams, pipeline behaviors, processors, and exception handling

0.1.0Apache-2.01

InstallClose

MediatorTest.pudu

Pudu134 lines8.3 KB

GitHub ↗
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 Shared1516/// Runs the suite.17fn 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