{"action":"created","comment":{"url":"https://api.github.com/repos/msiegenthaler/free-event-sourcing/pulls/comments/63287825","id":63287825,"diff_hunk":"@@ -0,0 +1,105 @@\n+package freeeventsourcing\n+\n+import java.time.Instant\n+import cats.data.{ State, StateT, Xor, XorT }\n+import cats.free.Free\n+import cats.{ Monad, ~> }\n+import freeeventsourcing.EventSelector.WithEventType\n+import freeeventsourcing.Process.ProcessMonad\n+import freeeventsourcing.ProcessAction._\n+import org.scalatest.matchers.{ MatchResult, Matcher }\n+\n+class ProcessTestSupport[BC <: BoundedContext](boundedContext: BC) {\n+  def runFrom(expectations: Expectation*) =\n+    ProcessMatcher(expectations.toList, _ ⇒ Xor.right(()))\n+  def runFromWithResult(expectations: Expectation*)(toResult: Any) =\n+    ProcessMatcher(expectations.toList, r ⇒\n+      if (r == toResult) Xor.right(()) else Xor.left(s\"produced non matching result: ${r} instead of ${toResult}\"))\n+\n+  case class ProcessMatcher(expectations: List[Expectation], checkResult: Any ⇒ String Xor Unit) extends Matcher[M[_]] {\n+    def apply(process: M[_]) = {\n+      val x = process.foldMap(Implementation.Transform)\n+      val y = x\n+        .run(expectations)\n+        .leftMap(error ⇒ s\"did not match the expectations: ${error}\")\n+        .flatMap(r ⇒ if (r._1.isEmpty) Xor.right(r._2) else Xor.left(s\"rmaining expectations: ${r._1}\"))\n+        .flatMap(checkResult)\n+      new MatchResult(\n+        y.isRight,\n+        y.fold(identity, _ ⇒ \"?\"),\n+        s\"matched the process expectations but should not have\"\n+      )\n+    }\n+  }\n+\n+  sealed trait Expectation\n+  case class ExpectAwaitEvent[S <: WithEventType](selector: S, result: S#Event) extends Expectation\n+  object ExpectAwaitEvent {\n+    def create[S <: WithEventType](selector: S)(result: selector.Event): ExpectAwaitEvent[S] =\n+      ExpectAwaitEvent(selector, result)\n+  }\n+  case class ExpectWaitUntil(instant: Instant) extends Expectation\n+  case object ExpectEnd extends Expectation\n+  case class ExpectCommand[A <: Aggregate, Cmd <: A#Command](\n+    aggregateType: A, aggregate: A#Id, command: A#Command, result: Cmd#Error Xor Unit\n+  ) extends Expectation\n+  object ExpectCommand {\n+    def create[A <: Aggregate, C <: A#Command](aggregateType: A, aggregate: A#Id, command: C)(result: command.Error Xor Unit): ExpectCommand[A, command.type] =\n+      ExpectCommand(aggregateType, aggregate, command, result)\n+  }\n+\n+  type Action[+A] = ProcessAction[BC, A]\n+  type M[A] = ProcessMonad[BC, A]\n+\n+  private[this] object Implementation {\n+    type OrFail[A] = Xor[String, A]\n+    type Expectations[A] = StateT[OrFail, List[Expectation], A]\n+    object Expectations {\n+      def next(received: String): Expectations[Expectation] = lift {\n+        case h :: t ⇒ Xor.right((t, h))\n+        case Nil    ⇒ Xor.left(s\"No more expectations, but got ${received}\")\n+      }\n+      def check[A](action: ProcessAction[BC, A]): Expectations[A] = next(action.toString).flatMap { exp ⇒\n+        val r: String Xor A = (action, exp) match {\n+          case (AwaitEvent(s1), ExpectAwaitEvent(s2, result)) ⇒\n+            if (s1 == s2) Xor.right(result)\n+            else Xor.left(s\"AwaitEvent: selector mismatch ${s1} != ${s2}\")\n+          case (WaitUntil(i1), ExpectWaitUntil(i2)) ⇒\n+            if (i1 == i2) Xor.right(())\n+            else Xor.left(s\"WaitUntil: time is different: ${i1} != ${i2}\")\n+          case (End(), ExpectEnd) ⇒\n+            Xor.right(())\n+          case (Execute(at1, a1, c1, errorHandler), ExpectCommand(at2, a2, c2, r)) ⇒\n+            if (at1 != at2) Xor.left(s\"Execute: Aggregate type is different: ${at1} != ${at2}\")\n+            else if (a1 != a2) Xor.left(s\"Execute: Aggregate id is different: ${a1} != ${a2}\")\n+            else if (c1 != c2) Xor.left(s\"Execute: Command is different: ${c1} != ${c2}\")\n+            else {\n+              Xor.right {\n+                r.fold(\n+                  //TODO actually we should evaluate recursivly...\n+                  //                e ⇒ errorHandler(e.asInstanceOf), //not really save, but don't care in tests..\n+                  e ⇒ (), //not really save, but don't care in tests..\n+                  _ ⇒ ()\n+                )\n+              }\n+            }\n+\n+          //TODO Other expectations\n+          //        case FirstOf(alternatives) ⇒ ???\n+          case _ ⇒ ???","path":"src/test/scala/freeeventsourcing/ProcessTestSupport.scala","position":89,"original_position":89,"commit_id":"913f99929e0b50df3a6c67a6b75fafefd597ddc4","original_commit_id":"913f99929e0b50df3a6c67a6b75fafefd597ddc4","user":{"login":"msiegenthaler","id":261358,"avatar_url":"https://avatars.githubusercontent.com/u/261358?v=3","gravatar_id":"","url":"https://api.github.com/users/msiegenthaler","html_url":"https://github.com/msiegenthaler","followers_url":"https://api.github.com/users/msiegenthaler/followers","following_url":"https://api.github.com/users/msiegenthaler/following{/other_user}","gists_url":"https://api.github.com/users/msiegenthaler/gists{/gist_id}","starred_url":"https://api.github.com/users/msiegenthaler/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/msiegenthaler/subscriptions","organizations_url":"https://api.github.com/users/msiegenthaler/orgs","repos_url":"https://api.github.com/users/msiegenthaler/repos","events_url":"https://api.github.com/users/msiegenthaler/events{/privacy}","received_events_url":"https://api.github.com/users/msiegenthaler/received_events","type":"User","site_admin":false},"body":"![Codacy](https://www.codacy.com/assets/images/favicon.png) Issue found: [Usage of ??? operator](https://www.codacy.com/app/msiegenthaler/free-event-sourcing/file/2574156993/issues/source?bid=3298199&fileBranchId=3298202#l89)","created_at":"2016-05-15T08:00:23Z","updated_at":"2016-05-15T08:00:23Z","html_url":"https://github.com/msiegenthaler/free-event-sourcing/pull/7#discussion_r63287825","pull_request_url":"https://api.github.com/repos/msiegenthaler/free-event-sourcing/pulls/7","_links":{"self":{"href":"https://api.github.com/repos/msiegenthaler/free-event-sourcing/pulls/comments/63287825"},"html":{"href":"https://github.com/msiegenthaler/free-event-sourcing/pull/7#discussion_r63287825"},"pull_request":{"href":"https://api.github.com/repos/msiegenthaler/free-event-sourcing/pulls/7"}}},"pull_request":{"url":"https://api.github.com/repos/msiegenthaler/free-event-sourcing/pulls/7","id":70065853,"html_url":"https://github.com/msiegenthaler/free-event-sourcing/pull/7","diff_url":"https://github.com/msiegenthaler/free-event-sourcing/pull/7.diff","patch_url":"https://github.com/msiegenthaler/free-event-sourcing/pull/7.patch","issue_url":"https://api.github.com/repos/msiegenthaler/free-event-sourcing/issues/7","number":7,"state":"open","locked":false,"title":"Split Process into interface and syntax","user":{"login":"msiegenthaler","id":261358,"avatar_url":"https://avatars.githubusercontent.com/u/261358?v=3","gravatar_id":"","url":"https://api.github.com/users/msiegenthaler","html_url":"https://github.com/msiegenthaler","followers_url":"https://api.github.com/users/msiegenthaler/followers","following_url":"https://api.github.com/users/msiegenthaler/following{/other_user}","gists_url":"https://api.github.com/users/msiegenthaler/gists{/gist_id}","starred_url":"https://api.github.com/users/msiegenthaler/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/msiegenthaler/subscriptions","organizations_url":"https://api.github.com/users/msiegenthaler/orgs","repos_url":"https://api.github.com/users/msiegenthaler/repos","events_url":"https://api.github.com/users/msiegenthaler/events{/privacy}","received_events_url":"https://api.github.com/users/msiegenthaler/received_events","type":"User","site_admin":false},"body":"","created_at":"2016-05-13T22:14:26Z","updated_at":"2016-05-15T08:00:23Z","closed_at":null,"merged_at":null,"merge_commit_sha":"91a68f80b963ba4e240fd89a39a39929c04cf618","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/msiegenthaler/free-event-sourcing/pulls/7/commits","review_comments_url":"https://api.github.com/repos/msiegenthaler/free-event-sourcing/pulls/7/comments","review_comment_url":"https://api.github.com/repos/msiegenthaler/free-event-sourcing/pulls/comments{/number}","comments_url":"https://api.github.com/repos/msiegenthaler/free-event-sourcing/issues/7/comments","statuses_url":"https://api.github.com/repos/msiegenthaler/free-event-sourcing/statuses/913f99929e0b50df3a6c67a6b75fafefd597ddc4","head":{"label":"msiegenthaler:process-syntax","ref":"process-syntax","sha":"913f99929e0b50df3a6c67a6b75fafefd597ddc4","user":{"login":"msiegenthaler","id":261358,"avatar_url":"https://avatars.githubusercontent.com/u/261358?v=3","gravatar_id":"","url":"https://api.github.com/users/msiegenthaler","html_url":"https://github.com/msiegenthaler","followers_url":"https://api.github.com/users/msiegenthaler/followers","following_url":"https://api.github.com/users/msiegenthaler/following{/other_user}","gists_url":"https://api.github.com/users/msiegenthaler/gists{/gist_id}","starred_url":"https://api.github.com/users/msiegenthaler/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/msiegenthaler/subscriptions","organizations_url":"https://api.github.com/users/msiegenthaler/orgs","repos_url":"https://api.github.com/users/msiegenthaler/repos","events_url":"https://api.github.com/users/msiegenthaler/events{/privacy}","received_events_url":"https://api.github.com/users/msiegenthaler/received_events","type":"User","site_admin":false},"repo":{"id":48706311,"name":"free-event-sourcing","full_name":"msiegenthaler/free-event-sourcing","owner":{"login":"msiegenthaler","id":261358,"avatar_url":"https://avatars.githubusercontent.com/u/261358?v=3","gravatar_id":"","url":"https://api.github.com/users/msiegenthaler","html_url":"https://github.com/msiegenthaler","followers_url":"https://api.github.com/users/msiegenthaler/followers","following_url":"https://api.github.com/users/msiegenthaler/following{/other_user}","gists_url":"https://api.github.com/users/msiegenthaler/gists{/gist_id}","starred_url":"https://api.github.com/users/msiegenthaler/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/msiegenthaler/subscriptions","organizations_url":"https://api.github.com/users/msiegenthaler/orgs","repos_url":"https://api.github.com/users/msiegenthaler/repos","events_url":"https://api.github.com/users/msiegenthaler/events{/privacy}","received_events_url":"https://api.github.com/users/msiegenthaler/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/msiegenthaler/free-event-sourcing","description":"Purely functional Event Sourcing based on Domain Driven Design and implemented using Free Monads.","fork":false,"url":"https://api.github.com/repos/msiegenthaler/free-event-sourcing","forks_url":"https://api.github.com/repos/msiegenthaler/free-event-sourcing/forks","keys_url":"https://api.github.com/repos/msiegenthaler/free-event-sourcing/keys{/key_id}","collaborators_url":"https://api.github.com/repos/msiegenthaler/free-event-sourcing/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/msiegenthaler/free-event-sourcing/teams","hooks_url":"https://api.github.com/repos/msiegenthaler/free-event-sourcing/hooks","issue_events_url":"https://api.github.com/repos/msiegenthaler/free-event-sourcing/issues/events{/number}","events_url":"https://api.github.com/repos/msiegenthaler/free-event-sourcing/events","assignees_url":"https://api.github.com/repos/msiegenthaler/free-event-sourcing/assignees{/user}","branches_url":"https://api.github.com/repos/msiegenthaler/free-event-sourcing/branches{/branch}","tags_url":"https://api.github.com/repos/msiegenthaler/free-event-sourcing/tags","blobs_url":"https://api.github.com/repos/msiegenthaler/free-event-sourcing/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/msiegenthaler/free-event-sourcing/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/msiegenthaler/free-event-sourcing/git/refs{/sha}","trees_url":"https://api.github.com/repos/msiegenthaler/free-event-sourcing/git/trees{/sha}","statuses_url":"https://api.github.com/repos/msiegenthaler/free-event-sourcing/statuses/{sha}","languages_url":"https://api.github.com/repos/msiegenthaler/free-event-sourcing/languages","stargazers_url":"https://api.github.com/repos/msiegenthaler/free-event-sourcing/stargazers","contributors_url":"https://api.github.com/repos/msiegenthaler/free-event-sourcing/contributors","subscribers_url":"https://api.github.com/repos/msiegenthaler/free-event-sourcing/subscribers","subscription_url":"https://api.github.com/repos/msiegenthaler/free-event-sourcing/subscription","commits_url":"https://api.github.com/repos/msiegenthaler/free-event-sourcing/commits{/sha}","git_commits_url":"https://api.github.com/repos/msiegenthaler/free-event-sourcing/git/commits{/sha}","comments_url":"https://api.github.com/repos/msiegenthaler/free-event-sourcing/comments{/number}","issue_comment_url":"https://api.github.com/repos/msiegenthaler/free-event-sourcing/issues/comments{/number}","contents_url":"https://api.github.com/repos/msiegenthaler/free-event-sourcing/contents/{+path}","compare_url":"https://api.github.com/repos/msiegenthaler/free-event-sourcing/compare/{base}...{head}","merges_url":"https://api.github.com/repos/msiegenthaler/free-event-sourcing/merges","archive_url":"https://api.github.com/repos/msiegenthaler/free-event-sourcing/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/msiegenthaler/free-event-sourcing/downloads","issues_url":"https://api.github.com/repos/msiegenthaler/free-event-sourcing/issues{/number}","pulls_url":"https://api.github.com/repos/msiegenthaler/free-event-sourcing/pulls{/number}","milestones_url":"https://api.github.com/repos/msiegenthaler/free-event-sourcing/milestones{/number}","notifications_url":"https://api.github.com/repos/msiegenthaler/free-event-sourcing/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/msiegenthaler/free-event-sourcing/labels{/name}","releases_url":"https://api.github.com/repos/msiegenthaler/free-event-sourcing/releases{/id}","deployments_url":"https://api.github.com/repos/msiegenthaler/free-event-sourcing/deployments","created_at":"2015-12-28T18:27:01Z","updated_at":"2016-05-13T00:20:29Z","pushed_at":"2016-05-15T07:58:12Z","git_url":"git://github.com/msiegenthaler/free-event-sourcing.git","ssh_url":"git@github.com:msiegenthaler/free-event-sourcing.git","clone_url":"https://github.com/msiegenthaler/free-event-sourcing.git","svn_url":"https://github.com/msiegenthaler/free-event-sourcing","homepage":"","size":250,"stargazers_count":1,"watchers_count":1,"language":"Scala","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":1,"forks":0,"open_issues":1,"watchers":1,"default_branch":"master"}},"base":{"label":"msiegenthaler:master","ref":"master","sha":"95b9bdd0f22dc1a4b0fe83e27a03393976827f32","user":{"login":"msiegenthaler","id":261358,"avatar_url":"https://avatars.githubusercontent.com/u/261358?v=3","gravatar_id":"","url":"https://api.github.com/users/msiegenthaler","html_url":"https://github.com/msiegenthaler","followers_url":"https://api.github.com/users/msiegenthaler/followers","following_url":"https://api.github.com/users/msiegenthaler/following{/other_user}","gists_url":"https://api.github.com/users/msiegenthaler/gists{/gist_id}","starred_url":"https://api.github.com/users/msiegenthaler/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/msiegenthaler/subscriptions","organizations_url":"https://api.github.com/users/msiegenthaler/orgs","repos_url":"https://api.github.com/users/msiegenthaler/repos","events_url":"https://api.github.com/users/msiegenthaler/events{/privacy}","received_events_url":"https://api.github.com/users/msiegenthaler/received_events","type":"User","site_admin":false},"repo":{"id":48706311,"name":"free-event-sourcing","full_name":"msiegenthaler/free-event-sourcing","owner":{"login":"msiegenthaler","id":261358,"avatar_url":"https://avatars.githubusercontent.com/u/261358?v=3","gravatar_id":"","url":"https://api.github.com/users/msiegenthaler","html_url":"https://github.com/msiegenthaler","followers_url":"https://api.github.com/users/msiegenthaler/followers","following_url":"https://api.github.com/users/msiegenthaler/following{/other_user}","gists_url":"https://api.github.com/users/msiegenthaler/gists{/gist_id}","starred_url":"https://api.github.com/users/msiegenthaler/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/msiegenthaler/subscriptions","organizations_url":"https://api.github.com/users/msiegenthaler/orgs","repos_url":"https://api.github.com/users/msiegenthaler/repos","events_url":"https://api.github.com/users/msiegenthaler/events{/privacy}","received_events_url":"https://api.github.com/users/msiegenthaler/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/msiegenthaler/free-event-sourcing","description":"Purely functional Event Sourcing based on Domain Driven Design and implemented using Free Monads.","fork":false,"url":"https://api.github.com/repos/msiegenthaler/free-event-sourcing","forks_url":"https://api.github.com/repos/msiegenthaler/free-event-sourcing/forks","keys_url":"https://api.github.com/repos/msiegenthaler/free-event-sourcing/keys{/key_id}","collaborators_url":"https://api.github.com/repos/msiegenthaler/free-event-sourcing/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/msiegenthaler/free-event-sourcing/teams","hooks_url":"https://api.github.com/repos/msiegenthaler/free-event-sourcing/hooks","issue_events_url":"https://api.github.com/repos/msiegenthaler/free-event-sourcing/issues/events{/number}","events_url":"https://api.github.com/repos/msiegenthaler/free-event-sourcing/events","assignees_url":"https://api.github.com/repos/msiegenthaler/free-event-sourcing/assignees{/user}","branches_url":"https://api.github.com/repos/msiegenthaler/free-event-sourcing/branches{/branch}","tags_url":"https://api.github.com/repos/msiegenthaler/free-event-sourcing/tags","blobs_url":"https://api.github.com/repos/msiegenthaler/free-event-sourcing/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/msiegenthaler/free-event-sourcing/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/msiegenthaler/free-event-sourcing/git/refs{/sha}","trees_url":"https://api.github.com/repos/msiegenthaler/free-event-sourcing/git/trees{/sha}","statuses_url":"https://api.github.com/repos/msiegenthaler/free-event-sourcing/statuses/{sha}","languages_url":"https://api.github.com/repos/msiegenthaler/free-event-sourcing/languages","stargazers_url":"https://api.github.com/repos/msiegenthaler/free-event-sourcing/stargazers","contributors_url":"https://api.github.com/repos/msiegenthaler/free-event-sourcing/contributors","subscribers_url":"https://api.github.com/repos/msiegenthaler/free-event-sourcing/subscribers","subscription_url":"https://api.github.com/repos/msiegenthaler/free-event-sourcing/subscription","commits_url":"https://api.github.com/repos/msiegenthaler/free-event-sourcing/commits{/sha}","git_commits_url":"https://api.github.com/repos/msiegenthaler/free-event-sourcing/git/commits{/sha}","comments_url":"https://api.github.com/repos/msiegenthaler/free-event-sourcing/comments{/number}","issue_comment_url":"https://api.github.com/repos/msiegenthaler/free-event-sourcing/issues/comments{/number}","contents_url":"https://api.github.com/repos/msiegenthaler/free-event-sourcing/contents/{+path}","compare_url":"https://api.github.com/repos/msiegenthaler/free-event-sourcing/compare/{base}...{head}","merges_url":"https://api.github.com/repos/msiegenthaler/free-event-sourcing/merges","archive_url":"https://api.github.com/repos/msiegenthaler/free-event-sourcing/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/msiegenthaler/free-event-sourcing/downloads","issues_url":"https://api.github.com/repos/msiegenthaler/free-event-sourcing/issues{/number}","pulls_url":"https://api.github.com/repos/msiegenthaler/free-event-sourcing/pulls{/number}","milestones_url":"https://api.github.com/repos/msiegenthaler/free-event-sourcing/milestones{/number}","notifications_url":"https://api.github.com/repos/msiegenthaler/free-event-sourcing/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/msiegenthaler/free-event-sourcing/labels{/name}","releases_url":"https://api.github.com/repos/msiegenthaler/free-event-sourcing/releases{/id}","deployments_url":"https://api.github.com/repos/msiegenthaler/free-event-sourcing/deployments","created_at":"2015-12-28T18:27:01Z","updated_at":"2016-05-13T00:20:29Z","pushed_at":"2016-05-15T07:58:12Z","git_url":"git://github.com/msiegenthaler/free-event-sourcing.git","ssh_url":"git@github.com:msiegenthaler/free-event-sourcing.git","clone_url":"https://github.com/msiegenthaler/free-event-sourcing.git","svn_url":"https://github.com/msiegenthaler/free-event-sourcing","homepage":"","size":250,"stargazers_count":1,"watchers_count":1,"language":"Scala","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":1,"forks":0,"open_issues":1,"watchers":1,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/msiegenthaler/free-event-sourcing/pulls/7"},"html":{"href":"https://github.com/msiegenthaler/free-event-sourcing/pull/7"},"issue":{"href":"https://api.github.com/repos/msiegenthaler/free-event-sourcing/issues/7"},"comments":{"href":"https://api.github.com/repos/msiegenthaler/free-event-sourcing/issues/7/comments"},"review_comments":{"href":"https://api.github.com/repos/msiegenthaler/free-event-sourcing/pulls/7/comments"},"review_comment":{"href":"https://api.github.com/repos/msiegenthaler/free-event-sourcing/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/msiegenthaler/free-event-sourcing/pulls/7/commits"},"statuses":{"href":"https://api.github.com/repos/msiegenthaler/free-event-sourcing/statuses/913f99929e0b50df3a6c67a6b75fafefd597ddc4"}}}}