From 7b9ab00118a12261def517ed9a67bd9e9e2a3fbd Mon Sep 17 00:00:00 2001
From: Andy Chambers <andy.chambers@fundingcircle.com>
Date: Tue, 11 Feb 2020 15:29:52 +0000
Subject: [PATCH 1/8] Add functions to generate the most commonly used commands

---
 src/jackdaw/test/commands.clj      | 22 +++++++++++++++++++++-
 src/jackdaw/test/commands/base.clj |  2 +-
 2 files changed, 22 insertions(+), 2 deletions(-)

diff --git a/src/jackdaw/test/commands.clj b/src/jackdaw/test/commands.clj
index d493c461..ea5b6709 100644
--- a/src/jackdaw/test/commands.clj
+++ b/src/jackdaw/test/commands.clj
@@ -3,7 +3,8 @@
   (:require
    [jackdaw.test.commands.base :as base]
    [jackdaw.test.commands.write :as write]
-   [jackdaw.test.commands.watch :as watch]))
+   [jackdaw.test.commands.watch :as watch])
+  (:refer-clojure :exclude [do]))
 
 (def base-commands base/command-map)
 (def write-command write/command-map)
@@ -37,3 +38,22 @@
   [machine handler]
   (assoc machine
          :command-handler handler))
+
+(defn do [do-fn]
+  `[:do ~do-fn])
+
+(defn do! [do-fn]
+  `[:do! ~do-fn])
+
+(defn write!
+  ([topic-id message]
+   `[:write! ~topic-id ~message])
+
+  ([topic-id message options]
+   `[:write! ~topic-id ~message ~options]))
+
+(defn watch
+  ([watch-query]
+   `[:watch ~watch-query])
+  ([watch-query opts]
+   `[:watch ~watch-query ~opts]))
diff --git a/src/jackdaw/test/commands/base.clj b/src/jackdaw/test/commands/base.clj
index 1fbe22d7..18443b7a 100644
--- a/src/jackdaw/test/commands/base.clj
+++ b/src/jackdaw/test/commands/base.clj
@@ -1,6 +1,6 @@
 (ns jackdaw.test.commands.base
   (:require
-    [clojure.pprint :as pprint]))
+   [clojure.pprint :as pprint]))
 
 (def command-map
   {:stop (constantly true)

From 22802424195016bc3a1dea966a42999b6bb6b0b9 Mon Sep 17 00:00:00 2001
From: Andy Chambers <andy.chambers@fundingcircle.com>
Date: Wed, 12 Feb 2020 13:59:37 +0000
Subject: [PATCH 2/8] Update the test-machine integration tests to use the
 command api

---
 test/jackdaw/test_test.clj | 73 +++++++++++++++++++-------------------
 1 file changed, 37 insertions(+), 36 deletions(-)

diff --git a/test/jackdaw/test_test.clj b/test/jackdaw/test_test.clj
index 123ee9ff..6bae0c7d 100644
--- a/test/jackdaw/test_test.clj
+++ b/test/jackdaw/test_test.clj
@@ -3,6 +3,7 @@
    [clojure.test :refer :all]
    [jackdaw.streams :as k]
    [jackdaw.test :as jd.test]
+   [jackdaw.test.commands :as cmd]
    [jackdaw.test.fixtures :as fix]
    [jackdaw.test.serde :as serde]
    [jackdaw.test.transports :as trns]
@@ -115,10 +116,9 @@
   (testing "write then watch"
     (fix/with-fixtures [(fix/topic-fixture kafka-config {"foo" foo-topic})]
       (with-open [t (jd.test/test-machine (kafka-transport))]
-        (let [write [:write! "foo" {:id "msg1" :payload "yolo"}]
-              watch [:watch (by-id "foo" "msg1")
-                     {:info "failed to find foo with id=msg1"}]
-
+        (let [write (cmd/write! "foo" {:id "msg1" :payload "yolo"})
+              watch (cmd/watch (by-id "foo" "msg1")
+                               {:info "failed to find foo with id=msg1"})
               {:keys [results journal]} (jd.test/run-test t [write watch])
               [write-result watch-result] results]
 
@@ -139,13 +139,13 @@
 (deftest test-reuse-machine
   (fix/with-fixtures [(fix/topic-fixture kafka-config {"foo" foo-topic})]
     (with-open [t (jd.test/test-machine (kafka-transport))]
-      (let [prog1 [[:write! "foo" {:id "msg2" :payload "yolo"}]
-                   [:watch (by-id "foo" "msg2")
-                    {:info "failed to find foo with id=msg2"}]]
+      (let [prog1 [(cmd/write! "foo" {:id "msg2" :payload "yolo"})
+                   (cmd/watch (by-id "foo" "msg2")
+                              {:info "failed to find foo with id=msg2"})]
 
-            prog2 [[:write! "foo" {:id "msg3" :payload "you only live twice"}]
-                   [:watch (by-id "foo" "msg3")
-                    {:info "failed to find foo with id=msg3"}]]]
+            prog2 [(cmd/write! "foo" {:id "msg3" :payload "you only live twice"})
+                   (cmd/watch (by-id "foo" "msg3")
+                              {:info "failed to find foo with id=msg3"})]]
 
         (testing "run test sequence and inspect results"
           (let [{:keys [results journal]} (jd.test/run-test t prog1)]
@@ -207,14 +207,15 @@
                                                            :out test-out}})
         (fn [machine]
           (jd.test/run-test machine
-                            [[:write! :in {:id "1" :payload "foo"} {:key-fn :id}]
-                             [:write! :in {:id "2" :payload "bar"} {:key-fn :id}]
-                             [:watch (fn [journal]
-                                       (when (->> (get-in journal [:topics :out])
-                                                  (filter (fn [r]
-                                                            (= (get-in r [:value :id]) "2")))
-                                                  (not-empty))
-                                         true)) {:timeout 2000}]])))
+                            [(cmd/write! :in {:id "1" :payload "foo"} {:key-fn :id})
+                             (cmd/write! :in {:id "2" :payload "bar"} {:key-fn :id})
+                             (cmd/watch (fn [journal]
+                                          (when (->> (get-in journal [:topics :out])
+                                                     (filter (fn [r]
+                                                               (= (get-in r [:value :id]) "2")))
+                                                     (not-empty))
+                                            true))
+                                        {:timeout 2000})])))
       (catch Exception e
         (reset! error-raised e)))
     (is (not @error-raised))))
@@ -230,13 +231,13 @@
                                                            :out test-out}})
         (fn [machine]
           (jd.test/run-test machine
-                            [[:write! :in {:id "1" :payload "foo"} {:key-fn :id}]
-                             [:write! :in {:id "2" :payload "bar"} {:key-fn :id}]
-                             [:watch (fn [journal]
-                                       (->> (get-in journal [:topics :out])
-                                            (filter (fn [r]
-                                                      (= (:id r) "2")))
-                                            (not-empty)))]])))
+                            [(cmd/write! :in {:id "1" :payload "foo"} {:key-fn :id})
+                             (cmd/write! :in {:id "2" :payload "bar"} {:key-fn :id})
+                             (cmd/watch (fn [journal]
+                                          (->> (get-in journal [:topics :out])
+                                               (filter (fn [r]
+                                                         (= (:id r) "2")))
+                                               (not-empty))))])))
       (catch Exception e
         (reset! error-raised e)))
     (is @error-raised)))
@@ -252,13 +253,13 @@
                                                            :out test-out}})
         (fn [machine]
           (jd.test/run-test machine
-                            [[:write! :in {:id "1" :payload "foo"} {:key-fn bad-key-fn}]
-                             [:write! :in {:id "2" :payload "bar"} {:key-fn :id}]
-                             [:watch (fn [journal]
-                                       (->> (get-in journal [:topics :out])
-                                            (filter (fn [r]
-                                                      (= (:id r) "2")))
-                                            (not-empty)))]])))
+                            [(cmd/write! :in {:id "1" :payload "foo"} {:key-fn bad-key-fn})
+                             (cmd/write! :in {:id "2" :payload "bar"} {:key-fn :id})
+                             (cmd/watch (fn [journal]
+                                          (->> (get-in journal [:topics :out])
+                                               (filter (fn [r]
+                                                         (= (:id r) "2")))
+                                               (not-empty))))])))
       (catch Exception e
         (reset! error-raised e)))
     (is @error-raised)))
@@ -274,10 +275,10 @@
                                                            :out test-out}})
         (fn [machine]
           (jd.test/run-test machine
-                            [[:write! :in {:id "1" :payload "foo"} {:key-fn :id}]
-                             [:write! :in {:id "2" :payload "bar"} {:key-fn :id}]
-                             [:watch (fn [journal]
-                                       (bad-watch-fn journal))]])))
+                            [(cmd/write! :in {:id "1" :payload "foo"} {:key-fn :id})
+                             (cmd/write! :in {:id "2" :payload "bar"} {:key-fn :id})
+                             (cmd/watch (fn [journal]
+                                          (bad-watch-fn journal)))])))
       (catch Exception e
         (reset! error-raised e)))
     (is @error-raised)))

From 45de7a0cefa3af27ea0bdac45403a1f0ff1976d2 Mon Sep 17 00:00:00 2001
From: Andy Chambers <andy.chambers@fundingcircle.com>
Date: Wed, 12 Feb 2020 14:31:51 +0000
Subject: [PATCH 3/8] Add basic fspecs for the test commands

---
 src/jackdaw/test/commands.clj | 25 +++++++++++++++++++++++++
 1 file changed, 25 insertions(+)

diff --git a/src/jackdaw/test/commands.clj b/src/jackdaw/test/commands.clj
index ea5b6709..79b50f9c 100644
--- a/src/jackdaw/test/commands.clj
+++ b/src/jackdaw/test/commands.clj
@@ -1,6 +1,7 @@
 (ns jackdaw.test.commands
   ""
   (:require
+   [clojure.spec.alpha :as s]
    [jackdaw.test.commands.base :as base]
    [jackdaw.test.commands.write :as write]
    [jackdaw.test.commands.watch :as watch])
@@ -39,12 +40,27 @@
   (assoc machine
          :command-handler handler))
 
+;; Test Command API
+
+(s/def ::topic-id keyword?)
+(s/def ::test-msg any?)
+(s/def ::write-options map?)
+(s/def ::watch-options map?)
+
 (defn do [do-fn]
   `[:do ~do-fn])
 
+(s/fdef do
+  :args ifn?
+  :ret vector?)
+
 (defn do! [do-fn]
   `[:do! ~do-fn])
 
+(s/fdef do!
+  :args ifn?
+  :ret vector?)
+
 (defn write!
   ([topic-id message]
    `[:write! ~topic-id ~message])
@@ -52,8 +68,17 @@
   ([topic-id message options]
    `[:write! ~topic-id ~message ~options]))
 
+(s/fdef write!
+  :args (s/cat ::topic-id ::write-options)
+  :ret vector?)
+
 (defn watch
   ([watch-query]
    `[:watch ~watch-query])
   ([watch-query opts]
    `[:watch ~watch-query ~opts]))
+
+(s/fdef watch
+  :args (s/cat ::watch-query ifn?
+               ::opts ::watch-options)
+  :ret vector?)

From 1414d15405a8a514e387dc88d1c92a1ab9ffab6c Mon Sep 17 00:00:00 2001
From: Andy Chambers <andy.chambers@fundingcircle.com>
Date: Wed, 12 Feb 2020 14:38:15 +0000
Subject: [PATCH 4/8] The topic-id in write commands can also be a string

---
 src/jackdaw/test/commands.clj | 11 +++++++----
 1 file changed, 7 insertions(+), 4 deletions(-)

diff --git a/src/jackdaw/test/commands.clj b/src/jackdaw/test/commands.clj
index 79b50f9c..2e5a2f33 100644
--- a/src/jackdaw/test/commands.clj
+++ b/src/jackdaw/test/commands.clj
@@ -42,7 +42,8 @@
 
 ;; Test Command API
 
-(s/def ::topic-id keyword?)
+(s/def ::topic-id (s/or ::keyword keyword?
+                        ::string string?))
 (s/def ::test-msg any?)
 (s/def ::write-options map?)
 (s/def ::watch-options map?)
@@ -69,7 +70,9 @@
    `[:write! ~topic-id ~message ~options]))
 
 (s/fdef write!
-  :args (s/cat ::topic-id ::write-options)
+  :args (s/cat :topic-id ::topic-id
+               :msg ::test-msg
+               :opts (s/? ::write-options))
   :ret vector?)
 
 (defn watch
@@ -79,6 +82,6 @@
    `[:watch ~watch-query ~opts]))
 
 (s/fdef watch
-  :args (s/cat ::watch-query ifn?
-               ::opts ::watch-options)
+  :args (s/cat :watch ifn?
+               :opts (s/? ::watch-options))
   :ret vector?)

From cc5c3fef1dc9a05a9482fbdc5abe9f43c8b43dc1 Mon Sep 17 00:00:00 2001
From: Andy Chambers <andy.chambers@fundingcircle.com>
Date: Wed, 12 Feb 2020 14:54:40 +0000
Subject: [PATCH 5/8] Use full names in specs for better docs

---
 src/jackdaw/test/commands.clj | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)

diff --git a/src/jackdaw/test/commands.clj b/src/jackdaw/test/commands.clj
index 2e5a2f33..f43e7a5f 100644
--- a/src/jackdaw/test/commands.clj
+++ b/src/jackdaw/test/commands.clj
@@ -44,7 +44,7 @@
 
 (s/def ::topic-id (s/or ::keyword keyword?
                         ::string string?))
-(s/def ::test-msg any?)
+(s/def ::test-message any?)
 (s/def ::write-options map?)
 (s/def ::watch-options map?)
 
@@ -71,17 +71,17 @@
 
 (s/fdef write!
   :args (s/cat :topic-id ::topic-id
-               :msg ::test-msg
-               :opts (s/? ::write-options))
+               :message ::test-msg
+               :options (s/? ::write-options))
   :ret vector?)
 
 (defn watch
   ([watch-query]
    `[:watch ~watch-query])
-  ([watch-query opts]
-   `[:watch ~watch-query ~opts]))
+  ([watch-query options]
+   `[:watch ~watch-query ~options]))
 
 (s/fdef watch
-  :args (s/cat :watch ifn?
-               :opts (s/? ::watch-options))
+  :args (s/cat :watch-query ifn?
+               :options (s/? ::watch-options))
   :ret vector?)

From a25fee06369f523db43b05c60c111bc295392be3 Mon Sep 17 00:00:00 2001
From: Andy Chambers <andy.chambers@fundingcircle.com>
Date: Wed, 12 Feb 2020 15:04:09 +0000
Subject: [PATCH 6/8] Update CHANGELOG

---
 CHANGELOG.md                  | 10 +++++++++-
 src/jackdaw/test/commands.clj |  2 +-
 2 files changed, 10 insertions(+), 2 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9b357dcd..8a793a6f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,14 @@
 # Changelog
 
-## Fixed
+## Unreleased
+
+### Added
+
+* Start formalizing test-machine commands with fspec'd functions
+
+## [0.7.2] - [2020-02-07]
+
+### Fixed
 
 * Fixed bug in Avro deserialisation, when handling a union of enum types
 
diff --git a/src/jackdaw/test/commands.clj b/src/jackdaw/test/commands.clj
index f43e7a5f..4ede842c 100644
--- a/src/jackdaw/test/commands.clj
+++ b/src/jackdaw/test/commands.clj
@@ -71,7 +71,7 @@
 
 (s/fdef write!
   :args (s/cat :topic-id ::topic-id
-               :message ::test-msg
+               :message ::test-message
                :options (s/? ::write-options))
   :ret vector?)
 

From 31d360cb9cf46dd63d14169d3691c0776b4a4c44 Mon Sep 17 00:00:00 2001
From: Andy Chambers <andy.chambers@fundingcircle.com>
Date: Wed, 12 Feb 2020 15:11:46 +0000
Subject: [PATCH 7/8] Ignore test-results/ directory

---
 .gitignore | 1 +
 1 file changed, 1 insertion(+)

diff --git a/.gitignore b/.gitignore
index 187fd397..acafb3cd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,6 +9,7 @@
 /jackdaw-[a-z]*/checkouts
 /jackdaw-[a-z]*/target
 /target
+/test-results
 /logs
 pom.xml
 pom.xml.asc

From 8cbc24d89fef2219a9fc0d7dc94a04fceac4cbf0 Mon Sep 17 00:00:00 2001
From: Andy Chambers <andy.chambers@fundingcircle.com>
Date: Wed, 12 Feb 2020 16:59:37 +0000
Subject: [PATCH 8/8] Add better docstrings for the command functions

---
 src/jackdaw/test/commands.clj | 49 ++++++++++++++++++++++++++++-------
 1 file changed, 40 insertions(+), 9 deletions(-)

diff --git a/src/jackdaw/test/commands.clj b/src/jackdaw/test/commands.clj
index 4ede842c..8dfbf379 100644
--- a/src/jackdaw/test/commands.clj
+++ b/src/jackdaw/test/commands.clj
@@ -42,20 +42,30 @@
 
 ;; Test Command API
 
-(s/def ::topic-id (s/or ::keyword keyword?
-                        ::string string?))
+(s/def ::topic-id (s/or :keyword keyword?
+                        :string string?))
 (s/def ::test-message any?)
 (s/def ::write-options map?)
 (s/def ::watch-options map?)
 
-(defn do [do-fn]
+(defn do
+  "Invoke the provided function, passing a snapshot of the test journal
+
+   Use this to perform side-effects without representing their result in the journal"
+  [do-fn]
   `[:do ~do-fn])
 
 (s/fdef do
   :args ifn?
   :ret vector?)
 
-(defn do! [do-fn]
+(defn do!
+  "Invoke the provided function, passing the journal `ref`
+
+   Use this to perform side-effects when you want to represent the result in the journal
+   (e.g. insert test-data into an external database AND into the journal with the expectation
+   that it will eventually appear in kafka via some external system like kafka-connect)"
+  [do-fn]
   `[:do! ~do-fn])
 
 (s/fdef do!
@@ -63,6 +73,17 @@
   :ret vector?)
 
 (defn write!
+  "Write a message to the topic identified in the topic-metadata by `topic-id`
+
+   `:message` is typically a map to be serialized by the Serde configured in the topic-metadata
+              but it can be whatever the configued Serde is capable of serializing
+   `:options` is an optional map containing additional information describing how the test-message
+              should be created. The following properties are supported.
+
+      `:key`             An explicit key to associate with the test message
+      `:key-fn`          A function to derive the key from the test message
+      `:partition`       The partition to which the test message should be written
+      `:partition-fn`    A function to derive the partition to which the test message should be written"
   ([topic-id message]
    `[:write! ~topic-id ~message])
 
@@ -76,12 +97,22 @@
   :ret vector?)
 
 (defn watch
-  ([watch-query]
-   `[:watch ~watch-query])
-  ([watch-query options]
-   `[:watch ~watch-query ~options]))
+  "Watch the test-journal until the `watch-fn` predicate returns true
+
+   `:watch-fn` is a function that takes the journal and returns true once the journal
+               contains evidence of the test being complete
+   `:options` is an optional map containing additional information describing how the watch
+              function will be run. The following properties are supported.
+
+      `:info` Diagnostic information to be included in the response when a watch fails
+              to observe the expected data in the journal
+      `:timeout` The number of milliseconds to wait before giving up"
+  ([watch-fn]
+   `[:watch ~watch-fn])
+  ([watch-fn options]
+   `[:watch ~watch-fn ~options]))
 
 (s/fdef watch
-  :args (s/cat :watch-query ifn?
+  :args (s/cat :watch-fn ifn?
                :options (s/? ::watch-options))
   :ret vector?)