From 01b532dfd33a66b544e8ba17b1b00d64ba532b7f Mon Sep 17 00:00:00 2001 From: SerhiiRI Date: Sun, 8 Mar 2026 00:36:58 +0200 Subject: [PATCH] - UPDATED Registry. Registry now apply vector and map registry forms - REMOVED build-compiler. Will be introduced other options to optimize secondary execution procedure. - ADDED Registry. To already built registry user can attach/remove additional CommandMapSpecs - RENAMED Core, function `create-registry` onto `registry-create` - ADDED Registry Tests. --- CHANGELOG.md | 13 + README.md | 24 +- src/commando/core.cljc | 172 +++--- src/commando/impl/finding_commands.cljc | 50 +- src/commando/impl/registry.cljc | 216 ++++---- src/commando/impl/utils.cljc | 2 +- test/unit/commando/commands/builtin_test.cljc | 90 +-- .../commando/commands/query_dsl_test.cljc | 10 +- test/unit/commando/core_test.cljc | 519 ++++++------------ test/unit/commando/impl/registry_test.clj | 96 ++++ 10 files changed, 574 insertions(+), 618 deletions(-) create mode 100644 test/unit/commando/impl/registry_test.clj diff --git a/CHANGELOG.md b/CHANGELOG.md index c7e490d..5ff9132 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ # 1.0.6 +REDESIGNED Registry. Registry is now a map-based structure (`{:type spec, ...}`) instead of a plain vector. `registry-create` accepts both formats: +```clojure +;; vector — order = scan priority +(registry-create [from-spec fn-spec]) +;; map — explicit keys +(registry-create {:commando/from from-spec, :commando/fn fn-spec}) +``` +Built registry can be modified with `registry-assoc` / `registry-dissoc` without rebuilding from scratch. + +RENAMED `create-registry` → `registry-create`. Old name removed. + +REMOVED `build-compiler`. Compiler concept removed from the pipeline; optimizations for repeated `execute` calls will be introduced in a future version. + ADDED `print-trace` in `commando.impl.utils` — replaces `print-deep-stats` with an improved flamegraph that also shows per-node instruction keys and optional title. Add `:__title` or `"__title"` to any instruction's top level to annotate that node in the output. `print-deep-stats` is kept as a deprecated alias. ADDED named anchor navigation for `:commando/from` paths. Declare an anchor with `"__anchor"` or `:__anchor` key in any instruction map, then reference it with `"@name"` as a path segment. The resolver walks up the tree and resolves to the nearest ancestor with that anchor name — independent of nesting depth. Anchors can be combined with existing `"../"` relative navigation in a single path. diff --git a/README.md b/README.md index a4d2f08..6152ef1 100644 --- a/README.md +++ b/README.md @@ -120,7 +120,7 @@ As Commando is simply a graph-based resolver with easy configuration, it is not The above function composes "Instructions", "Commands", and a "CommandRegistry". - **Instruction**: a Clojure map, large or small, containing data and _commands_. The instruction describes the data structure and the transformations to apply. - **Command**: a data-lexeme that is evaluated and returns a result. The rules for parsing and executing commands are flexible and customizable. Command `:command/from` return value by the absolute or relative path, can optionally apply a function provided under the `:=` key. -- **CommandRegistry**: a vector describing data-lexemes that should be treated as _commands_ by the library. +- **CommandRegistry**: a vector or map of CommandMapSpecs describing data-lexemes that should be treated as _commands_ by the library. When passed as a vector, the order defines the command scan priority. You can also pre-build a registry with `registry-create` and pass it directly. ### Builtin Functionality @@ -356,12 +356,10 @@ Let's create a new command using a CommandMapSpec configuration map: Now you can use it for more expressive operations like "summ=" and "multiply=" as shown below: ```clojure +;; Vector form — order defines scan priority (def command-registry - (commando/create-registry - [;; Add `:commando/from` - commands-builtin/command-from-spec - ;; Add `:CALC=` command to be handled - ;; inside instruction + (commando/registry-create + [commands-builtin/command-from-spec {:type :CALC= :recognize-fn #(and (map? %) (contains? % :CALC=)) :validate-params-fn (fn [m] @@ -372,6 +370,20 @@ Now you can use it for more expressive operations like "summ=" and "multiply=" a (apply (:CALC= m) (:ARGS m))) :dependencies {:mode :all-inside}}])) +;; Map form — explicit type keys +(def command-registry + (commando/registry-create + {:commando/from commands-builtin/command-from-spec + :CALC= {:type :CALC= + :recognize-fn #(and (map? %) (contains? % :CALC=)) + :validate-params-fn (fn [m] + (and + (fn? (:CALC= m)) + (not-empty (:ARGS m)))) + :apply (fn [_instruction _command m] + (apply (:CALC= m) (:ARGS m))) + :dependencies {:mode :all-inside}}})) + (commando/execute command-registry {"1" {:values {:a 1 :b -1}} diff --git a/src/commando/core.cljc b/src/commando/core.cljc index 6b1bd07..b51fc8e 100644 --- a/src/commando/core.cljc +++ b/src/commando/core.cljc @@ -1,5 +1,6 @@ (ns commando.core (:require + [commando.impl.command-map :as cm] [commando.impl.dependency :as deps] [commando.impl.executing :as executing] [commando.impl.finding-commands :as finding-commands] @@ -8,8 +9,15 @@ [commando.impl.status-map :as smap] [commando.impl.utils :as utils])) -(defn create-registry - "Creates a 'Command' registry from a vector of CommandMapSpecs: +;; -- Registry API -- + +(defn registry-create + "Creates a 'Command' registry from a map or vector of CommandMapSpecs. + + Accepts either: + - A map of {type -> CommandMapSpec} + - A vector of CommandMapSpecs (order defines command scan priority) + - An already-built registry (returned as-is) Each command specification (CommandMapSpec) should be a map containing at least: - `:type` - a unique keyword identifying the command type @@ -21,12 +29,12 @@ {:mode :all-inside} - all commands inside the current map are depednencies {:mode :none} - no dependencies, the other commands may depend from it. {:mode :point :point-key [:commando/from]} - special type of dependency - which declare that current command depends from the command it refer by + which declare that current command depends from the command it refer by exampled :commando/from key. Additional optional keys can include: - - `:validate-params-fn` - a function to validate command structures, and catch - invalid parameters at the anylisis stage. Only if the function + - `:validate-params-fn` - a function to validate command structures, and catch + invalid parameters at the anylisis stage. Only if the function return 'true' it ment that the command structure is valid. (fn [data] (throw ...)) => Failure (fn [data] {:reason \"why\"}) => Failure @@ -34,30 +42,67 @@ (fn [data] false ) => Failure (fn [data] true ) => OK - The function returns a built registry that can be used to resolve Instruction - - Example - (create-registry - [{:type :print :recognize-fn ... :execute-fn ...} - commando.commands.builtin/command-fn-spec - commando.commands.builtin/command-apply-spec - commando.commands.builtin/command-mutation-spec - commando.commands.builtin/command-resolve-spec])" - [registry] - (registry/build (vec registry))) + The function returns a built registry that can be used to resolve Instruction + + Example (map) + (registry-create + {:commando/from commando.commands.builtin/command-from-spec + :commando/fn commando.commands.builtin/command-fn-spec}) + + Example (vector — order defines scan priority) + (registry-create + [commando.commands.builtin/command-from-spec + commando.commands.builtin/command-fn-spec])" + ([registry] + (registry-create registry nil)) + ([registry opts] + (cond + (registry/built? registry) registry + (vector? registry) (let [specs-map (into {} (map (juxt :type identity)) registry) + order (mapv :type registry)] + (registry/build specs-map (merge opts {:registry-order order}))) + (map? registry) (registry/build registry opts) + :else (throw (ex-info "Registry must be a map, vector, or a built registry" + {:registry registry}))))) + +(defn registry-assoc + "Adds or replaces a CommandMapSpec in a built registry. + The spec is keyed by `command-map-spec-type` and appended to the scan order + if not already present. Revalidates the registry. + + Example: + (-> (registry-create {...}) + (registry-assoc :my/cmd my-cmd-spec))" + [built-registry command-map-spec-type command-map-spec] + (registry/registry-assoc built-registry command-map-spec-type command-map-spec)) + +(defn registry-dissoc + "Removes a CommandMapSpec from a built registry by its type key. + Updates the scan order accordingly. Revalidates the registry. + + Example: + (-> (registry-create {...}) + (registry-dissoc :my/cmd))" + [built-registry command-map-spec-type] + (registry/registry-dissoc built-registry command-map-spec-type)) + +;; -- Execute Flow -- (defn ^:private use-registry [status-map registry] (let [start-time (utils/now) result (case (:status status-map) :failed (-> status-map - (smap/status-map-handle-warning {:message "Skip step with registry check"})) + (smap/status-map-handle-warning {:message "Skip step with registry check"})) :ok (try (-> status-map - (assoc :registry (if (registry/built? registry) registry (create-registry registry)))) + (assoc :registry + (-> + (registry-create registry) + (registry/enreach-runtime-registry)))) (catch #?(:clj Exception :cljs :default) - e - (-> status-map + e + (-> status-map (smap/status-map-handle-error {:message "Invalid registry specification" :error (utils/serialize-exception e)})))))] (smap/status-map-add-measurement result "use-registry" start-time (utils/now)))) @@ -118,8 +163,7 @@ (smap/status-map-handle-warning {:message (str utils/exception-message-header "sort-entities-by-deps. Skipping mandatory step")})) :ok (let [sort-result (graph/topological-sort (:internal/cm-dependency status-map)) - status-map (assoc status-map :internal/cm-running-order (vec ;; (reverse (:sorted sort-result)) - (:sorted sort-result)))] + status-map (assoc status-map :internal/cm-running-order (vec (:sorted sort-result)))] (if (not-empty (:cyclic sort-result)) (smap/status-map-handle-error status-map {:message (str utils/exception-message-header @@ -158,72 +202,28 @@ (smap/status-map-handle-success {:message "All commands executed successfully"}))))))] (smap/status-map-add-measurement result "execute-commands!" start-time (utils/now)))) -(defn build-compiler - [registry instruction] - (let [status-map (-> (smap/status-map-pure {:instruction instruction}) - (utils/hook-process (:hook-execute-start (utils/execute-config))) - (use-registry registry) - (find-commands) - (build-deps-tree) - (sort-commands-by-deps))] - (case (:status status-map) - :failed (-> status-map - (smap/status-map-handle-warning {:message (str utils/exception-message-header - "build-compiler. Error building compiler")})) - :ok (cond-> status-map - true (update-in [:internal/cm-running-order] registry/remove-instruction-commands-from-command-vector) - (false? (:debug-result (utils/execute-config))) - (select-keys [:uuid - :status - :registry - :internal/cm-running-order - :successes - :warnings]))))) - -(defn ^:private compiler->status-map - "Cause compiler contains only two :registry and :internal/cm-running-order keys - they have to be added to status-map before it be executed." - [compiler] - (if (and (registry/built? (get compiler :registry)) - (contains? compiler :internal/cm-running-order) - (contains? compiler :status)) - (case (:status compiler) - :ok (if (true? (:debug-result (utils/execute-config))) - (-> - (smap/status-map-pure compiler)) - (-> - (smap/status-map-pure (select-keys compiler - [:uuid - :registry - :internal/cm-running-order - :successes - :warnings])))) - :failed compiler) - (-> - (smap/status-map-pure compiler) - (smap/status-map-handle-error {:message "Corrupted compiler structure"})))) - (defn execute - [registry-or-compiler instruction] - {:pre [(or (map? registry-or-compiler) (sequential? registry-or-compiler))]} + [registry instruction] + {:pre [(or (map? registry) (vector? registry))]} (binding [utils/*execute-internals* (utils/-execute-internals-push (str (random-uuid)))] - (let [ ;; Under (build-compiler) we ment the unfinished status map - start-time (utils/now) - status-map-with-compiler (-> (cond - (map? registry-or-compiler) - (-> - (compiler->status-map registry-or-compiler)) - (sequential? registry-or-compiler) - (-> - (build-compiler registry-or-compiler instruction) - (compiler->status-map))) - (assoc :instruction instruction))] - (cond-> (execute-commands! status-map-with-compiler) - (false? (:debug-result (utils/execute-config))) (dissoc :internal/cm-running-order) - (false? (:debug-result (utils/execute-config))) (dissoc :registry) - :always (smap/status-map-add-measurement "execute" start-time (utils/now)) - :always (utils/hook-process (:hook-execute-end (utils/execute-config))))))) + (let [start-time (utils/now) + status-map (-> (smap/status-map-pure {:instruction instruction}) + (utils/hook-process (:hook-execute-start (utils/execute-config))) + (use-registry registry) + (find-commands) + (build-deps-tree) + (sort-commands-by-deps))] + (let [status-map-ready + (case (:status status-map) + :failed status-map + :ok (-> status-map + (update :internal/cm-running-order registry/remove-runtime-registry-commands-from-command-list) + (update :registry registry/reset-runtime-registry)))] + (cond-> (execute-commands! (assoc status-map-ready :instruction instruction)) + (false? (:debug-result (utils/execute-config))) (dissoc :internal/cm-running-order) + (false? (:debug-result (utils/execute-config))) (dissoc :registry) + :always (smap/status-map-add-measurement "execute" start-time (utils/now)) + :always (utils/hook-process (:hook-execute-end (utils/execute-config)))))))) (defn failed? [status-map] (smap/failed? status-map)) (defn ok? [status-map] (smap/ok? status-map)) - diff --git a/src/commando/impl/finding_commands.cljc b/src/commando/impl/finding_commands.cljc index a93557c..4e4c881 100644 --- a/src/commando/impl/finding_commands.cljc +++ b/src/commando/impl/finding_commands.cljc @@ -44,7 +44,7 @@ "Finds and validates a command from registry that matches the given `value`. Returns the command-spec if match is found and valid, nil otherwise. Throws exception if match is found but validation fails." - [command-registry value path] + [command-spec-vector value path] (some (fn [command-spec] (when (command? command-spec value) (let [value-valid-return (command-valid? command-spec value)] @@ -69,31 +69,31 @@ :reason value-valid-return :path path :value value})))))) - command-registry)) + command-spec-vector)) (defn find-commands "Traverses the instruction tree (BFS algo) and collects all commands defined by the registry." [instruction command-registry] - (loop [queue (vec [[]]) - found-commands [] - debug-stack-map {}] - (if (empty? queue) - found-commands - (let [current-path (first queue) - remaining-paths (subvec queue 1) - current-value (get-in instruction current-path) - debug-stack (if (:debug-result (utils/execute-config)) (get debug-stack-map current-path (list)) (list))] - (if-let [command-spec (instruction-command-spec command-registry current-value current-path)] - (let [command (cm/->CommandMapPath - current-path - (if (:debug-result (utils/execute-config)) (merge command-spec {:__debug_stack debug-stack}) command-spec)) - child-paths (command-child-paths command-spec current-value current-path) - updated-debug-stack-map (if (:debug-result (utils/execute-config)) - (reduce #(assoc %1 %2 (conj debug-stack command)) debug-stack-map child-paths) - {})] - (recur (into remaining-paths child-paths) (conj found-commands command) updated-debug-stack-map)) - ;; No match - traverse children if coll, skip if leaf - (recur (into remaining-paths (coll-child-paths current-value current-path)) - found-commands - debug-stack-map)))))) - + (let [command-spec-vector (:registry-runtime command-registry)] + (loop [queue (vec [[]]) + found-commands [] + debug-stack-map {}] + (if (empty? queue) + found-commands + (let [current-path (first queue) + remaining-paths (subvec queue 1) + current-value (get-in instruction current-path) + debug-stack (if (:debug-result (utils/execute-config)) (get debug-stack-map current-path (list)) (list))] + (if-let [command-spec (instruction-command-spec command-spec-vector current-value current-path)] + (let [command (cm/->CommandMapPath + current-path + (if (:debug-result (utils/execute-config)) (merge command-spec {:__debug_stack debug-stack}) command-spec)) + child-paths (command-child-paths command-spec current-value current-path) + updated-debug-stack-map (if (:debug-result (utils/execute-config)) + (reduce #(assoc %1 %2 (conj debug-stack command)) debug-stack-map child-paths) + {})] + (recur (into remaining-paths child-paths) (conj found-commands command) updated-debug-stack-map)) + ;; No match - traverse children if coll, skip if leaf + (recur (into remaining-paths (coll-child-paths current-value current-path)) + found-commands + debug-stack-map))))))) diff --git a/src/commando/impl/registry.cljc b/src/commando/impl/registry.cljc index 71cdfdf..9715dcd 100644 --- a/src/commando/impl/registry.cljc +++ b/src/commando/impl/registry.cljc @@ -1,19 +1,47 @@ (ns commando.impl.registry "API for registry. - A registry is a collection of command specifications that define how to - recognize, validate, and execute commands found in instruction map." + A registry is a map-based collection of command specifications that define how to + recognize, validate, and execute commands found in instruction map. + + Input (user-facing): + (registry/build + {:commando/fn cmds/command-fn-spec + :commando/from cmds/command-from-spec} + {:registry-order [:commando/from :commando/fn]}) + + Output (built registry): + {:registry {:commando/fn spec1, :commando/from spec2} + :registry-order [:commando/from :commando/fn] + :registry-validated 1709654400000 + :registry-hash 12345}" (:require [commando.impl.command-map :as cm])) -(defn- find-duplicate-types - "Returns a seq of duplicate :type values in the given command specs." - [command-specs] - (->> command-specs - (map :type) - (frequencies) - (filter (fn [[_ count]] (> count 1))) - (map first))) +(defn- validate-registry + "Validates all specs in the registry map. + Returns {:valid? true} or {:valid? false :errors [...]}" + [specs-map] + (let [empty-errors (when (empty? specs-map) + [{:type :empty-command-specs + :message "Registry is empty"}]) + validation-errors (reduce-kv + (fn [errors type spec] + (if-let [error (:error (cm/validate-command-spec spec))] + (conj errors {:type :invalid-spec + :command-map-spec/type type + :message error}) + (if (not= type (:type spec)) + (conj errors {:type :type-mismatch + :command-map-spec/type type + :message (str "Registry key " type " does not match spec :type " (:type spec))}) + errors))) + [] + specs-map) + all-errors (concat validation-errors empty-errors)] + (if (empty? all-errors) + {:valid? true} + {:valid? false :errors (vec all-errors)}))) (def ^:private default-command-value-spec {:type :instruction/_value @@ -30,104 +58,92 @@ :recognize-fn vector? :apply (fn [_ _ _m] (throw (ex-info "Command :instruction/vec should not be evaluated" {}))) :dependencies {:mode :all-inside}}) -(def ^:private -cm-type-instruction-defaults - (into #{} - (map :type - [default-command-vec-spec - default-command-map-spec - default-command-value-spec]))) - -(defn attach-instruction-commands [registry] - (let [registry-meta (meta registry)] - (with-meta - (into (vec registry) - [default-command-vec-spec - default-command-map-spec - default-command-value-spec]) - registry-meta))) - -(defn detach-instruction-commands [registry] - (let [registry-meta (meta registry)] - (with-meta - (reduce (fn [acc e] - (if - (contains? - #{default-command-vec-spec - default-command-map-spec - default-command-value-spec} - e) - acc - (conj acc e))) - [] - registry) - registry-meta))) - -(defn remove-instruction-commands-from-command-vector [cm-vector] - (reduce (fn [acc command-map] - (if (contains? -cm-type-instruction-defaults (:type (cm/command-data command-map))) - acc (conj acc command-map))) - [] cm-vector)) -(defn- validate-registry - "Validates: - - All specs are valid according to CommandMapSpec - - No duplicate :type values +(def ^:private internal-command-specs + [default-command-vec-spec + default-command-map-spec + default-command-value-spec]) - Returns {:valid? true} or {:valid? false :errors [...]}" - [command-specs] - (let [empty-command-spec-list-errors (when (empty? command-specs) - [{:type :empty-command-specs - :message "Registry is empty"}]) - validation-errors (reduce (fn [errors spec] - (if-let [error (:error (cm/validate-command-spec spec))] - (conj errors {:type :invalid-spec - :command-map-spec/type (:type spec) - :message error}) - errors)) - [] - command-specs) - duplicates (find-duplicate-types command-specs) - duplicate-errors (map (fn [dup-type] - {:type :duplicate-type - :command-map-spec/type dup-type - :message (str "Duplicate command type: " dup-type)}) - duplicates) - all-errors (concat - validation-errors - duplicate-errors - empty-command-spec-list-errors)] - (if (empty? all-errors) - {:valid? true} - {:valid? false :errors all-errors}))) +(defn- compute-registry-order + "Computes the scan order: ordered keys first, then remaining keys in arbitrary order." + [specs-map ordered-keys] + (let [spec-keys (set (keys specs-map)) + valid-ordered (filterv spec-keys ordered-keys) + remaining (remove (set valid-ordered) (keys specs-map))] + (into valid-ordered remaining))) (defn built? - "Returns true if the given value is a properly built registry. - - Built registry mean that registry was validated and the internal - Instruction commands were attached to list of command specifications." + "Returns true if the given value is a properly built registry map." [registry] - (and (vector? registry) - (:registry/validated (meta registry)))) + (and + (map? registry) + (some? (:registry-validated registry)) + (some? (:registry-hash registry)) + (contains? registry :registry) + (contains? registry :registry-order))) (defn build - "Builds a command registry from a sequence of command specifications. - - Validates all specs and returns a registry that can be used with execute. - The registry is marked with metadata to enable caching of compilation results. + "Builds a command registry from a map of {type -> spec}. Args: - command-spec-list - A sequence of command specifications + specs-map - A map of {:type spec, ...} + opts - Optional map with :registry-order [...] for scan ordering Returns: - A validated registry vector with metadata for caching or throws an error" - [command-spec-list] - {:pre [(vector? command-spec-list)]} - (let [validation (validate-registry command-spec-list)] - (if (:valid? validation) - (let [command-spec-list (attach-instruction-commands command-spec-list)] - (with-meta command-spec-list - {:registry/validated true - :registry/hash (hash command-spec-list)})) - (throw (ex-info "Invalid registry specification" - {:errors (:errors validation) - :registry command-spec-list}))))) + A validated registry map or throws an error" + ([specs-map] (build specs-map nil)) + ([specs-map opts] + (let [validation (validate-registry specs-map)] + (if (:valid? validation) + (let [ordered-keys (compute-registry-order specs-map (:registry-order opts))] + {:registry specs-map + :registry-order ordered-keys + :registry-validated #?(:clj (System/currentTimeMillis) + :cljs (.now js/Date)) + :registry-hash (hash specs-map)}) + (throw + (ex-info "Invalid registry specification" + {:errors (:errors validation) + :registry specs-map})))))) + +(defn enreach-runtime-registry [built-registry] + (let [registry (:registry built-registry) + registry-order (:registry-order built-registry)] + (assoc built-registry + :registry-runtime + (into (mapv registry registry-order) + internal-command-specs)))) + +(defn reset-runtime-registry [enreached-registry] + (dissoc enreached-registry :registry-runtime)) + +(defn remove-runtime-registry-commands-from-command-list [cm-vector] + (let [cm-type-instruction-defaults + (into #{} (map :type internal-command-specs))] + (reduce (fn [acc command-map] + (if (contains? cm-type-instruction-defaults (:type (cm/command-data command-map))) + acc (conj acc command-map))) + [] cm-vector))) + +;; ---------------- +;; Registry Helpers +;; ---------------- + +(defn registry-assoc + "Adds or replaces a spec in a built registry. Revalidates." + [built-registry command-map-spec-type command-map-spec] + (let [new-specs (assoc (:registry built-registry) command-map-spec-type command-map-spec) + old-order (:registry-order built-registry) + new-order (if (some #{command-map-spec-type} old-order) + old-order + (conj old-order command-map-spec-type))] + (build new-specs {:registry-order new-order}))) + +(defn registry-dissoc + "Removes a spec from a built registry. Revalidates." + [built-registry command-map-spec-type] + (let [new-specs (dissoc (:registry built-registry) command-map-spec-type) + new-order (filterv #(not= % command-map-spec-type) (:registry-order built-registry))] + (build new-specs {:registry-order new-order}))) + + diff --git a/src/commando/impl/utils.cljc b/src/commando/impl/utils.cljc index 4664bf6..e2aa06a 100644 --- a/src/commando/impl/utils.cljc +++ b/src/commando/impl/utils.cljc @@ -98,7 +98,7 @@ See `commando.core/execute` `commando.core/execute-commands!`(binding)" - [] (or *command-map-spec-registry* [])) + [] (or *command-map-spec-registry* {})) ;; ------------------ ;; Function Resolvers diff --git a/test/unit/commando/commands/builtin_test.cljc b/test/unit/commando/commands/builtin_test.cljc index f69e283..7d3afb2 100644 --- a/test/unit/commando/commands/builtin_test.cljc +++ b/test/unit/commando/commands/builtin_test.cljc @@ -17,8 +17,8 @@ (is (= {:vec1 [1 2 3], :vec2 [3 2 1], :result-simple 10, :result-with-deps 10} (:instruction - (commando/execute [command-builtin/command-fn-spec - command-builtin/command-from-spec] + (commando/execute {:commando/fn command-builtin/command-fn-spec + :commando/from command-builtin/command-from-spec} {:vec1 [1 2 3] :vec2 [3 2 1] :result-simple {:commando/fn (fn [& [v1 v2]] (reduce + (map * v1 v2))) @@ -34,7 +34,7 @@ (binding [commando-utils/*execute-config* {:debug-result false :error-data-string false}] - (commando/execute [command-builtin/command-fn-spec] + (commando/execute {:commando/fn command-builtin/command-fn-spec} {:commando/fn "STRING" :args [[1 2 3] [3 2 1]]})) (fn [error] @@ -53,7 +53,7 @@ (binding [commando-utils/*execute-config* {:debug-result false :error-data-string false}] - (commando/execute [command-builtin/command-fn-spec] + (commando/execute {:commando/fn command-builtin/command-fn-spec} {:commando/fn (fn [& [v1 v2]] (reduce + (map * v1 v2))) :args "BROKEN"})) (fn [error] @@ -73,8 +73,8 @@ (is (= {:value 1, :result-simple 2, :result-with-deps 2} (:instruction - (commando/execute [command-builtin/command-apply-spec - command-builtin/command-from-spec] + (commando/execute {:commando/apply command-builtin/command-apply-spec + :commando/from command-builtin/command-from-spec} {:value 1 :result-simple {:commando/apply {:value 1} := (fn [e] (-> e :value inc))} @@ -87,7 +87,7 @@ (binding [commando-utils/*execute-config* {:debug-result false :error-data-string false}] - (commando/execute [command-builtin/command-apply-spec] + (commando/execute {:commando/apply command-builtin/command-apply-spec} {:commando/apply {:value 1} := "STRING"})) (fn [error] @@ -113,8 +113,8 @@ (testing "Successfull test cases" (is (= {:a 1, :vec 1, :vec-map 1, :result-of-another 1} (get-in - (commando/execute [command-builtin/command-fn-spec - command-builtin/command-from-spec] + (commando/execute {:commando/fn command-builtin/command-fn-spec + :commando/from command-builtin/command-from-spec} {"values" {:a 1 :vec [1] :vec-map [{:a 1}] @@ -132,7 +132,7 @@ :d {:result [4 4]}, :e {:result [5 5]}} (:instruction - (commando/execute [command-builtin/command-from-spec] + (commando/execute {:commando/from command-builtin/command-from-spec} {:a {:value 1 :result {:commando/from ["../" :value]}} :b {:value 2 @@ -150,7 +150,7 @@ "d" {"result" [4 4]}, "e" {"result" [5 5]}} (:instruction - (commando/execute [command-builtin/command-from-spec] + (commando/execute {:commando/from command-builtin/command-from-spec} {"a" {"value" 1 "result" {"commando-from" ["../" "value"]}} "b" {"value" 2 @@ -165,8 +165,8 @@ #?(:clj (is (= {:=-keyword 1, :=-fn 2, :=-symbol 2, :=-var 2} (get-in - (commando/execute [command-builtin/command-fn-spec - command-builtin/command-from-spec] + (commando/execute {:commando/fn command-builtin/command-fn-spec + :commando/from command-builtin/command-from-spec} {"value" {:kwd 1} "result" {:=-keyword {:commando/from ["value" ] := :kwd} :=-fn {:commando/from ["value"] := (fn [{:keys [kwd]}] (inc kwd))} @@ -177,8 +177,8 @@ "Uncorrect commando/from ':=' applicator. CLJ Supports: fn/keyword/var/symbol") :cljs (is (= {:=-keyword 1, :=-fn 2} (get-in - (commando/execute [command-builtin/command-fn-spec - command-builtin/command-from-spec] + (commando/execute {:commando/fn command-builtin/command-fn-spec + :commando/from command-builtin/command-from-spec} {"value" {:kwd 1} "result" {:=-keyword {:commando/from ["value" ] := :kwd} :=-fn {:commando/from ["value"] := (fn [{:keys [kwd]}] (inc kwd))}}}) @@ -189,7 +189,7 @@ (testing "Anchor navigation" (is (= {:section {:__anchor "root" :price 10 :ref 10}} (:instruction - (commando/execute [command-builtin/command-from-spec] + (commando/execute {:commando/from command-builtin/command-from-spec} {:section {:__anchor "root" :price 10 :ref {:commando/from ["@root" :price]}}}))) @@ -197,7 +197,7 @@ (is (= {:items [{:__anchor "item" :price 10 :ref 10} {:__anchor "item" :price 20 :ref 20}]} (:instruction - (commando/execute [command-builtin/command-from-spec] + (commando/execute {:commando/from command-builtin/command-from-spec} {:items [{:__anchor "item" :price 10 :ref {:commando/from ["@item" :price]}} @@ -209,7 +209,7 @@ :base-price 5 :section {:__anchor "section" :price 10 :sibling-price 5}}} (:instruction - (commando/execute [command-builtin/command-from-spec] + (commando/execute {:commando/from command-builtin/command-from-spec} {:catalog {:__anchor "root" :base-price 5 :section {:__anchor "section" @@ -226,7 +226,7 @@ {:price-1 5 :price-2 10}}}} (:instruction - (commando/execute [command-builtin/command-from-spec] + (commando/execute {:commando/from command-builtin/command-from-spec} {:root-1 {:__anchor "root-1" :price 5 @@ -241,7 +241,7 @@ (testing "Failure test cases" (is (helpers/status-map-contains-error? - (commando/execute [command-builtin/command-from-spec] + (commando/execute {:commando/from command-builtin/command-from-spec} {:ref {:commando/from ["@nonexistent" :value]}}) {:message "Commando. Point dependency failed: key ':commando/from' references non-existent path [\"@nonexistent\" :value]", :path [:ref], @@ -249,7 +249,7 @@ "Anchor not found: should produce error with :anchor key in data") (is (helpers/status-map-contains-error? - (commando/execute [command-builtin/command-from-spec] + (commando/execute {:commando/from command-builtin/command-from-spec} {"source" {:a 1 :b 2} "missing" {:commando/from ["UNEXISING"]}}) {:message "Commando. Point dependency failed: key ':commando/from' references non-existent path [\"UNEXISING\"]", @@ -258,7 +258,7 @@ "Waiting on error, bacause commando/from seding to unexising path") (is (helpers/status-map-contains-error? - (commando/execute [command-builtin/command-from-spec] + (commando/execute {:commando/from command-builtin/command-from-spec} {"source" {:a 1 :b 2} "missing" {"commando-from" ["UNEXISING"]}}) {:message "Commando. Point dependency failed: key 'commando-from' references non-existent path [\"UNEXISING\"]", @@ -270,7 +270,7 @@ (binding [commando-utils/*execute-config* {:debug-result false :error-data-string false}] - (commando/execute [command-builtin/command-from-spec] + (commando/execute {:commando/from command-builtin/command-from-spec} {"value" 1 "result" {:commando/from ["value"] "commando-from" ["value"]}})) @@ -287,7 +287,7 @@ {:debug-result false :error-data-string false}] (commando/execute - [command-builtin/command-from-spec] + {:commando/from command-builtin/command-from-spec} {:commando/from "BROKEN"})) (fn [error] (= @@ -302,7 +302,7 @@ {:debug-result false :error-data-string false}] (commando/execute - [command-builtin/command-from-spec] + {:commando/from command-builtin/command-from-spec} {:v 1 :a {:commando/from [:v] := ["BROKEN"]}})) (fn [error] @@ -335,8 +335,8 @@ (is (= {:vector1 [1 2 3], :vector2 [3 2 1], :result-simple 10, :result-with-deps 10} (:instruction - (commando/execute [command-builtin/command-mutation-spec - command-builtin/command-from-spec] + (commando/execute {:commando/mutation command-builtin/command-mutation-spec + :commando/from command-builtin/command-from-spec} {:vector1 [1 2 3] :vector2 [3 2 1] :result-simple {:commando/mutation :dot-product @@ -349,8 +349,8 @@ (is (= {"vector1" [1 2 3], "vector2" [3 2 1], "result-simple" 10, "result-with-deps" 10} (:instruction - (commando/execute [command-builtin/command-mutation-spec - command-builtin/command-from-spec] + (commando/execute {:commando/mutation command-builtin/command-mutation-spec + :commando/from command-builtin/command-from-spec} {"vector1" [1 2 3] "vector2" [3 2 1] "result-simple" {"commando-mutation" "dot-product" @@ -366,7 +366,7 @@ (binding [commando-utils/*execute-config* {:debug-result false :error-data-string false}] - (commando/execute [command-builtin/command-mutation-spec] + (commando/execute {:commando/mutation command-builtin/command-mutation-spec} {:commando/mutation :dot-product "commando-mutation" "dot-product" "vector1" [1 2 3] @@ -388,7 +388,7 @@ (binding [commando-utils/*execute-config* {:debug-result false :error-data-string false}] - (commando/execute [command-builtin/command-mutation-spec] + (commando/execute {:commando/mutation command-builtin/command-mutation-spec} {:commando/mutation (fn [] "BROKEN")})) (fn [error] (= @@ -402,7 +402,7 @@ (binding [commando-utils/*execute-config* {:debug-result false :error-data-string false}] - (commando/execute [command-builtin/command-mutation-spec] + (commando/execute {:commando/mutation command-builtin/command-mutation-spec} {:commando/mutation :dot-product :vector1 [1 "_" 3] :vector2 [3 2 1]})) @@ -461,10 +461,10 @@ {:vector-dot-1 32, :vector-dot-2 320} (:instruction (commando/execute - [command-builtin/command-macro-spec - command-builtin/command-fn-spec - command-builtin/command-from-spec - command-builtin/command-apply-spec] + {:commando/macro command-builtin/command-macro-spec + :commando/fn command-builtin/command-fn-spec + :commando/from command-builtin/command-from-spec + :commando/apply command-builtin/command-apply-spec} {:vector-dot-1 {:commando/macro :string-vectors-dot-product :vector1-str ["1" "2" "3"] @@ -479,10 +479,10 @@ {"vector-dot-1" 32, "vector-dot-2" 320} (:instruction (commando/execute - [command-builtin/command-macro-spec - command-builtin/command-fn-spec - command-builtin/command-from-spec - command-builtin/command-apply-spec] + {:commando/macro command-builtin/command-macro-spec + :commando/fn command-builtin/command-fn-spec + :commando/from command-builtin/command-from-spec + :commando/apply command-builtin/command-apply-spec} {"vector-dot-1" {"commando-macro" "string-vectors-dot-product" "vector1-str" ["1" "2" "3"] @@ -499,10 +499,10 @@ {:debug-result false :error-data-string false}] (commando/execute - [command-builtin/command-macro-spec - command-builtin/command-fn-spec - command-builtin/command-from-spec - command-builtin/command-apply-spec] + {:commando/macro command-builtin/command-macro-spec + :commando/fn command-builtin/command-fn-spec + :commando/from command-builtin/command-from-spec + :commando/apply command-builtin/command-apply-spec} {:commando/macro :string-vectors-dot-product "commando-macro" "string-vectors-dot-product" "vector1-str" ["1" "2" "3"] @@ -524,7 +524,7 @@ (binding [commando-utils/*execute-config* {:debug-result false :error-data-string false}] - (commando/execute [command-builtin/command-macro-spec] + (commando/execute {:commando/macro command-builtin/command-macro-spec} {:commando/macro (fn [])})) (fn [error] (= diff --git a/test/unit/commando/commands/query_dsl_test.cljc b/test/unit/commando/commands/query_dsl_test.cljc index eef651e..b22f2d7 100644 --- a/test/unit/commando/commands/query_dsl_test.cljc +++ b/test/unit/commando/commands/query_dsl_test.cljc @@ -38,11 +38,11 @@ :permissions ["remove-doc"]}]}) (def registry - (commando/create-registry - [command-query-dsl/command-resolve-spec - command-builtin/command-fn-spec - command-builtin/command-from-spec - command-builtin/command-apply-spec])) + (commando/registry-create + {:commando/resolve command-query-dsl/command-resolve-spec + :commando/fn command-builtin/command-fn-spec + :commando/from command-builtin/command-from-spec + :commando/apply command-builtin/command-apply-spec})) (defn get-permissions-by-name [permission-name] (first (filter (fn [x] (= permission-name (:permission-name x))) (:permissions db)))) diff --git a/test/unit/commando/core_test.cljc b/test/unit/commando/core_test.cljc index dbc7d79..7c891fd 100644 --- a/test/unit/commando/core_test.cljc +++ b/test/unit/commando/core_test.cljc @@ -6,7 +6,8 @@ [commando.core :as commando] [commando.impl.command-map :as cm] [commando.test-helpers :as helpers] - [malli.core :as malli])) + [malli.core :as malli] + [commando.impl.registry :as commando-registry])) (def test-add-id-command @@ -15,7 +16,12 @@ :apply (fn [_instruction _command-path-obj command-map] (assoc command-map :id :test-id)) :dependencies {:mode :all-inside}}) -(def registry [cmds-builtin/command-from-spec test-add-id-command]) +(def registry + (-> + {:commando/from cmds-builtin/command-from-spec + :test/add-id test-add-id-command} + (commando-registry/build) + (commando-registry/enreach-runtime-registry))) (def fail-validation-command {:type :fail-validation @@ -31,193 +37,87 @@ :dependencies {:mode :all-inside}}) (deftest find-commands - (testing "Edge cases" - (is (= [] - (:internal/cm-list (#'commando/find-commands + (testing "Basic cases" + (is (= [(cm/->CommandMapPath [] #'commando-registry/default-command-map-spec)] + (:internal/cm-list (#'commando/find-commands {:status :ok :instruction {} :registry registry}))) - "Empty instruction map gives empty command list") - (is (= [] - (:internal/cm-list (#'commando/find-commands + "Empty instruction return _map command") + (is (= [(cm/->CommandMapPath [] #'commando-registry/default-command-map-spec) + (cm/->CommandMapPath [:some-val] #'commando-registry/default-command-map-spec) + (cm/->CommandMapPath [:some-other] #'commando-registry/default-command-value-spec) + (cm/->CommandMapPath [:my-value] #'commando-registry/default-command-value-spec) + (cm/->CommandMapPath [:i] #'commando-registry/default-command-map-spec) + (cm/->CommandMapPath [:v] #'commando-registry/default-command-vec-spec) + (cm/->CommandMapPath [:some-val :a] #'commando-registry/default-command-value-spec) + (cm/->CommandMapPath [:i :am] #'commando-registry/default-command-map-spec) + (cm/->CommandMapPath [:i :am :deep] #'commando-registry/default-command-value-spec)] + (:internal/cm-list (#'commando/find-commands {:status :ok :instruction {:some-val {:a 2} :some-other 3 :my-value :is-here - :i {:am {:deep :nested}}} + :i {:am {:deep :nested}} + :v []} :registry registry}))) - "Instruction with values but no commands return empty cm-list") - (is (= [] - (:internal/cm-list (#'commando/find-commands - {:status :ok - :instruction {:cmd {:commando/from [:a]} - :a 1} - :registry []}))) - "Empty registry finds nothing") - (is (= 0 - (count (:internal/cm-list (#'commando/find-commands - {:status :ok - :instruction {:set #{:commando/from [:target]} - :list (list {:commando/from [:target]}) - :target 42} - :registry registry})))) - "Does not traverse into sets or lists") - (is (= 1 - (count (:internal/cm-list (#'commando/find-commands - {:status :ok - :instruction {:set #{:not-found} - :list (list :not-found) - :valid [{:commando/from [:target]}] - :target 42} - :registry registry})))) - "Finds commands from vectors while ignores sets/lists") - (is (= [:valid 0] - (cm/command-path (first (:internal/cm-list (#'commando/find-commands - {:status :ok - :instruction {:set #{:not-found} - :list (list :not-found) - :valid [{:commando/from [:target]}] - :target 42} - :registry registry}))))) - "Correctly identifies vector-based command path") - (is (= 0 - (count (:internal/cm-list (#'commando/find-commands - {:status :ok - :instruction {:a nil - :b {} - :c []} - :registry registry})))) - "Nil values and empty containers don't produce commands") - (is (= 1 - (count (:internal/cm-list (#'commando/find-commands - {:status :ok - :instruction {:a nil - :b {} - :c [] - :valid {:commando/from [:target]} - :target 42} - :registry registry})))) - "Finds valid commands despite presence of nil/empty values")) - (testing "Basic cases" - (is (= [(cm/->CommandMapPath [:d] cmds-builtin/command-from-spec)] + "Instruction return internal commands _map, _vec, _value.") + (is (= [(cm/->CommandMapPath [] #'commando-registry/default-command-map-spec) + (cm/->CommandMapPath [:set] #'commando-registry/default-command-value-spec) + (cm/->CommandMapPath [:list] #'commando-registry/default-command-value-spec) + (cm/->CommandMapPath [:primitive] #'commando-registry/default-command-value-spec) + (cm/->CommandMapPath [:java-obj] #'commando-registry/default-command-value-spec)] (:internal/cm-list (#'commando/find-commands - {:status :ok - :instruction {:a 2 - :b {:c 5} - :d {:commando/from [:a]}} - :registry registry}))) - "Find one command") - (is (= [(cm/->CommandMapPath [] test-add-id-command)] - (:internal/cm-list (#'commando/find-commands - {:status :ok - :instruction {:test/add-id 5} - :registry registry}))) - "Whole instruction map is a command") - (is (= [(cm/->CommandMapPath [:d :first] cmds-builtin/command-from-spec) - (cm/->CommandMapPath [:d :second] cmds-builtin/command-from-spec)] - (:internal/cm-list (#'commando/find-commands - {:status :ok - :instruction {:a 2 - :b {:c 5} - :d {:first {:commando/from [:a]} - :second {:commando/from [:b :c]}}} - :registry registry}))) - "Find two commands in nested map") - (is (= [(cm/->CommandMapPath [:d 0] cmds-builtin/command-from-spec) - (cm/->CommandMapPath [:d 2] cmds-builtin/command-from-spec)] + {:status :ok + :instruction {:set #{:commando/from [:target]} + :list (list {:commando/from [:target]}) + :primitive 42 + :java-obj #?(:clj (java.util.Date.) + :cljs (js/Date.))} + :registry registry}))) + "Any type that not Map,Vector(and registry not contain other commands) became a _value standart internal command") + (is (= [(cm/->CommandMapPath [] #'commando-registry/default-command-map-spec) + (cm/->CommandMapPath [:set] #'commando-registry/default-command-value-spec) + (cm/->CommandMapPath [:list] #'commando-registry/default-command-value-spec) + (cm/->CommandMapPath [:valid] #'commando-registry/default-command-vec-spec) + (cm/->CommandMapPath [:target] #'commando-registry/default-command-value-spec) + (cm/->CommandMapPath [:valid 0] cmds-builtin/command-from-spec)] (:internal/cm-list (#'commando/find-commands + {:status :ok + :instruction {:set #{:not-found} + :list (list :not-found) + :valid [{:commando/from [:target]}] + :target 42} + :registry registry}))) + "commando/from find and returned with corresponding command-map-path object") + (is (= + [(cm/->CommandMapPath [] #'commando-registry/default-command-map-spec) + (cm/->CommandMapPath [:a] #'commando-registry/default-command-map-spec) + (cm/->CommandMapPath [:target] #'commando-registry/default-command-value-spec) + (cm/->CommandMapPath [:a "some"] #'commando-registry/default-command-map-spec) + (cm/->CommandMapPath [:a "some" :c] #'commando-registry/default-command-vec-spec) + (cm/->CommandMapPath [:a "some" :c 0] #'commando-registry/default-command-value-spec) + (cm/->CommandMapPath [:a "some" :c 1] cmds-builtin/command-from-spec)] + (:internal/cm-list (#'commando/find-commands {:status :ok - :instruction {:a 2 - :b {:c 5} - :d [{:commando/from [:a]} :some {:commando/from [:b :c]}]} + :instruction {:a {"some" {:c [:some {:commando/from [:target]}]}} + :target 42} :registry registry}))) - "Find two commands in vector") - (let [result (:internal/cm-list (#'commando/find-commands - {:status :ok - :instruction {:goal {:test/add-id :fn - :ref {:commando/from [:other]}} - :other "value"} - :registry registry}))] - (is (some #(= (cm/command-path %) [:goal]) result) "Finds :test/add-id command at [:goal] path") - (is (some #(= (cm/command-path %) [:goal :ref]) result) "Finds :commando/from command at [:goal :ref] path") - (is (some #(= (:type (cm/command-data %)) :test/add-id) result) "Correctly identifies :test/add-id command type") - (is (some #(= (:type (cm/command-data %)) :commando/from) result) - "Correctly identifies :commando/from command type")) - (let [deep-result (:internal/cm-list (#'commando/find-commands - {:status :ok - :instruction {:a {"some" {:c [:some {:commando/from [:target]}]}} - :target 42} - :registry registry}))] - (is (= 1 (count deep-result)) "Finds exactly one command in deeply nested structure") - (is (= [:a "some" :c 1] (cm/command-path (first deep-result))) - "Correctly identifies path 4 levels deep in different access structure, keyword/string/vector"))) - (testing "Self-referential commands" - (let [result (:internal/cm-list (#'commando/find-commands - {:status :ok - :instruction {:a {:b {:ref {:commando/from [:a :b]} - :data 42}} - :target "value"} - :registry registry}))] - (is (= 1 (count result)) "Finds self-referential command") - (is (= [:a :b :ref] (cm/command-path (first result))) "Correctly identifies path of self-referential command")) - (let [result (:internal/cm-list (#'commando/find-commands - {:status :ok - :instruction {:parent {:child {:ref {:commando/from [:parent]} - :value 10} - :data "test"}} - :registry registry}))] - (is (= 1 (count result)) "Finds command referencing parent path") - (is (= [:parent :child :ref] (cm/command-path (first result))) - "Correctly identifies path of parent-referential command")) - (let [result (:internal/cm-list (#'commando/find-commands - {:status :ok - :instruction {:a {:b {:ref1 {:commando/from [:a :b :ref2]} - :ref2 {:commando/from [:a :b :ref1]}}}} - :registry registry}))] - (is (= 2 (count result)) "Finds mutually referential commands") - (is (some #(= (cm/command-path %) [:a :b :ref1]) result) "Finds first mutually referential command") - (is (some #(= (cm/command-path %) [:a :b :ref2]) result) "Finds second mutually referential command"))) - (testing "Status handling" - (is (= :failed - (:status (#'commando/find-commands - {:status :failed - :instruction {:cmd {:commando/from [:a]}} - :registry registry}))) - "Failed status is preserved") + "Example of usage commando/from inside of deep map") (is (= :failed - (:status (#'commando/find-commands - {:status :ok - :instruction {:cmd {:should-fail true} - :b 2 - :a {:commando/from [:b]}} - :registry [fail-validation-command]}))) - "When validation of params fails find-commands ends with :failed") - (is (= :failed - (:status (#'commando/find-commands - {:status :ok - :instruction {:cmd {:any "value"}} - :registry [fail-recognize-command]}))) - "When recognize function throws exception find-commands ends with :failed") - (is (= :ok - (:status (#'commando/find-commands - {:status :ok - :instruction {:goal {:test/add-id :fn - :ref {:commando/from [:other]}} - :other "value"} - :registry registry}))) - "Success status when everything goes well")) - (testing "Path accuracy - handles different key types correctly" - (let [mixed-keys-result (:internal/cm-list (#'commando/find-commands - {:status :ok - :instruction {"string-key" {:commando/from [:a]} - :keyword-key {:commando/from [:a]} - 42 {:commando/from [:a]} - :a 1} - :registry registry}))] - (is (= 3 (count mixed-keys-result)) "Finds all commands with different key types") - (is (some #(= (cm/command-path %) ["string-key"]) mixed-keys-result) "Correctly handles string keys in paths") - (is (some #(= (cm/command-path %) [:keyword-key]) mixed-keys-result) "Correctly handles keyword keys in paths") - (is (some #(= (cm/command-path %) [42]) mixed-keys-result) "Correctly handles numeric keys in paths")))) + (:status (#'commando/find-commands {:status :failed}))) + "Failed status is preserved") + (is + (let [mixed-keys-result (:internal/cm-list (#'commando/find-commands + {:status :ok + :instruction {"string-key" {:commando/from [:a]} + :keyword-key {:commando/from [:a]} + 42 {:commando/from [:a]} + :a 1} + :registry registry}))] + (is (some #(= (cm/command-path %) ["string-key"]) mixed-keys-result) "Correctly handles string keys in paths") + (is (some #(= (cm/command-path %) [:keyword-key]) mixed-keys-result) "Correctly handles keyword keys in paths") + (is (some #(= (cm/command-path %) [42]) mixed-keys-result) "Correctly handles numeric keys in paths"))))) ; Test data for build-deps-tree (def cmd1 (cm/->CommandMapPath [:goal1] test-add-id-command)) @@ -419,9 +319,9 @@ :reports {:daily {:commando/from [:orders :process]} :weekly [{:commando/from [:reports :daily]} {:commando/from [:users :transform]}]}} large-test-commands (:internal/cm-list (#'commando/find-commands - {:status :ok - :instruction large-test-instruction - :registry registry})) + {:status :ok + :instruction large-test-instruction + :registry registry})) large-deps-status-map {:status :ok :instruction large-test-instruction :registry registry @@ -434,6 +334,8 @@ users-validate (cmd-by-path [:users :validate] large-test-commands) users-transform (cmd-by-path [:users :transform] large-test-commands) products-load (cmd-by-path [:products :load] large-test-commands) + products-load-items (cmd-by-path [:products :load :items] large-test-commands) + products-load-addid (cmd-by-path [:products :load :test/add-id] large-test-commands) products-items-fetch (cmd-by-path [:products :load :items :fetch] large-test-commands) products-items-enrich (cmd-by-path [:products :load :items :enrich] large-test-commands) products-cache (cmd-by-path [:products :cache] large-test-commands) @@ -443,6 +345,8 @@ orders-needs-create (cmd-by-path [:orders :finalize :needs-create] large-test-commands) orders-needs-prepare (cmd-by-path [:orders :finalize :needs-prepare] large-test-commands) orders-process (cmd-by-path [:orders :process] large-test-commands) + orders-process-steps (cmd-by-path [:orders :process :steps] large-test-commands) + orders-process-addid (cmd-by-path [:orders :process :test/add-id] large-test-commands) orders-steps-validate (cmd-by-path [:orders :process :steps :validate] large-test-commands) orders-steps-payment (cmd-by-path [:orders :process :steps :payment] large-test-commands) orders-steps-fulfill (cmd-by-path [:orders :process :steps :fulfill] large-test-commands) @@ -450,42 +354,43 @@ reports-weekly (cmd-by-path [:reports :weekly 0] large-test-commands) reports-weekly2 (cmd-by-path [:reports :weekly 1] large-test-commands)] (is (commando/ok? result) "Successfully processes large dependency tree") - (is (= 21 (count large-test-commands)) "Sanity input check: All 21 commands are present") - (is (= 21 (count deps)) "Dependency map contains all 21 commands") - (is (empty? (get deps config-db)) "config.database has no dependencies") - (is (empty? (get deps config-cache)) "config.cache has no dependencies") + (is (= 35 (count large-test-commands)) "Sanity input check: All 21 commands are present") + (is (= 35 (count deps)) "Dependency map contains all 21 commands") + (is (not-empty (get deps config-db)) "config.database has dependencies to itself") + (is (not-empty (get deps config-cache)) "config.cache has dependencies to itself") (is (contains? (get deps users-fetch) config-db) "users.fetch depends on config.database") (is (contains? (get deps products-items-fetch) config-db) "products.load.items.fetch depends on config.database") (is (contains? (get deps users-validate) users-fetch) "users.validate depends on users.fetch") (is (contains? (get deps users-transform) config-cache) "users.transform depends on config.cache") (is (contains? (get deps products-items-enrich) products-items-fetch) - "products.load.items.enrich depends on products.load.items.fetch") + "products.load.items.enrich depends on products.load.items.fetch") (is (contains? (get deps products-cache) products-load) - "products.cache pointing at products.load has as a dependency it pointed") + "products.cache pointing at products.load has as a dependency it pointed") (is - (= (get deps products-load) #{}) - "products.load has items.fetch (all-inside) child dep inside. But its still meen that products.load should not contain dependency, cause it depends only from internal structured values, and next this values has references to items.fetch.") + (= (get deps products-load) #{products-load-items products-load-addid}) + "products.load depends on all-inside children (items map and test/add-id value), not on external deps") (is (contains? (get deps orders-create) users-validate) "orders.create depends on users.validate") (is (contains? (get deps orders-prepare) products-cache) "orders.prepare depends on products.cache") (is (contains? (get deps orders-finalize) orders-needs-create) - "orders.finalize depends on needs-create (all-inside) - multi-reference dependencies via all-inside pattern") + "orders.finalize depends on needs-create (all-inside) - multi-reference dependencies via all-inside pattern") (is (contains? (get deps orders-finalize) orders-needs-prepare) - "orders.finalize depends on needs-prepare (all-inside) - multi-reference dependencies via all-inside pattern") + "orders.finalize depends on needs-prepare (all-inside) - multi-reference dependencies via all-inside pattern") (is (contains? (get deps orders-needs-create) orders-create) - "orders.finalize.needs-create depends on orders.create") + "orders.finalize.needs-create depends on orders.create") (is (contains? (get deps orders-needs-prepare) orders-prepare) - "orders.finalize.needs-prepare depends on orders.prepare") - (is (= (get deps orders-process) #{}) "orders.process hasn't dependency") + "orders.finalize.needs-prepare depends on orders.prepare") + (is (= (get deps orders-process) #{orders-process-steps orders-process-addid}) + "orders.process depends on all-inside children (steps map and test/add-id value)") (is (contains? (get deps orders-steps-validate) orders-create) - "orders.process.steps.validate depends on orders.create") + "orders.process.steps.validate depends on orders.create") (is (contains? (get deps orders-steps-payment) orders-steps-validate) - "orders.process.steps.payment depends on orders.process.steps.validate") + "orders.process.steps.payment depends on orders.process.steps.validate") (is (contains? (get deps orders-steps-fulfill) orders-steps-payment) - "orders.process.steps.fulfill depends on orders.process.steps.payment") + "orders.process.steps.fulfill depends on orders.process.steps.payment") (is (contains? (get deps reports-daily) orders-process) "reports.daily depends on orders.process") (is (contains? (get deps reports-weekly) reports-daily) "reports.weekly depends on reports.daily") (is (contains? (get deps reports-weekly2) users-transform) - "reports.weekly depends on users.transform (multi-path dependency)"))) + "reports.weekly depends on users.transform (multi-path dependency)"))) (testing "Circular dependencies" ;; TODO this will work here but to think about how it should be in ;; global exec test (especially after sort) @@ -536,7 +441,7 @@ none-cmd (cm/->CommandMapPath [:standalone] none-command) test-status-map {:status :ok :instruction {:standalone {:test/none :independent}} - :registry [none-command] + :registry {:test/none none-command} :internal/cm-list [none-cmd]} result (#'commando/build-deps-tree test-status-map) deps (:internal/cm-dependency result)] @@ -613,7 +518,7 @@ (def mutation-timestamp-execution-map {:status :ok :instruction {"timestamp" {:commando/mutation :time/current-dd-mm-yyyy-hh-mm-ss}} - :registry (commando/create-registry [cmds-builtin/command-mutation-spec]) + :registry (commando/registry-create {:commando/mutation cmds-builtin/command-mutation-spec}) :internal/cm-running-order [(cm/->CommandMapPath ["timestamp"] cmds-builtin/command-mutation-spec)]}) (def from-transformation-execution-map @@ -622,14 +527,14 @@ :extra "info"} "transformed" {:commando/from ["source"] := :data}} - :registry (commando/create-registry [cmds-builtin/command-from-spec]) + :registry (commando/registry-create {:commando/from cmds-builtin/command-from-spec}) :internal/cm-running-order [(cm/->CommandMapPath ["transformed"] cmds-builtin/command-from-spec)]}) (def apply-transformation-execution-map {:status :ok :instruction {"processed" {:commando/apply [1 2 3 4 5] := #(apply + %)}} - :registry (commando/create-registry [cmds-builtin/command-apply-spec]) + :registry (commando/registry-create {:commando/apply cmds-builtin/command-apply-spec}) :internal/cm-running-order [(cm/->CommandMapPath ["processed"] cmds-builtin/command-apply-spec)]}) ;; ================================ @@ -644,34 +549,34 @@ (def basic-success-map {:status :ok - :registry (commando/create-registry [test-add-id-command]) + :registry (commando/registry-create {:test/add-id test-add-id-command}) :internal/cm-running-order []}) (def from-command {:status :ok :instruction {"source" 42 "ref" {:commando/from ["source"]}} - :registry (commando/create-registry [cmds-builtin/command-from-spec]) + :registry (commando/registry-create {:commando/from cmds-builtin/command-from-spec}) :internal/cm-running-order [(cm/->CommandMapPath ["ref"] cmds-builtin/command-from-spec)]}) (def fn-command {:status :ok :instruction {"calc" {:commando/fn + :args [1 2 3]}} - :registry (commando/create-registry [cmds-builtin/command-fn-spec]) + :registry (commando/registry-create {:commando/fn cmds-builtin/command-fn-spec}) :internal/cm-running-order [(cm/->CommandMapPath ["calc"] cmds-builtin/command-fn-spec)]}) (def apply-command {:status :ok :instruction {"transform" {:commando/apply {"data" 10} := #(get % "data")}} - :registry (commando/create-registry [cmds-builtin/command-apply-spec]) + :registry (commando/registry-create {:commando/apply cmds-builtin/command-apply-spec}) :internal/cm-running-order [(cm/->CommandMapPath ["transform"] cmds-builtin/command-apply-spec)]}) (def add-id-command-execution {:status :ok :instruction {"cmd" {:test/add-id "some-value"}} - :registry (commando/create-registry [test-add-id-command]) + :registry (commando/registry-create {:test/add-id test-add-id-command}) :internal/cm-running-order [(cm/->CommandMapPath ["cmd"] test-add-id-command)]}) (def dependency-scenarios @@ -709,19 +614,19 @@ {:status :ok :instruction {"val" 10 "cmd" {:test/add-id "data"}} - :registry (commando/create-registry registry) + :registry (commando/registry-create registry) :internal/cm-running-order [(cm/->CommandMapPath ["cmd"] test-add-id-command)]}) (def timeout-command-execution-map {:status :ok :instruction {"cmd" {:fail true}} - :registry (commando/create-registry [(:timeout-cmd failing-commands)]) + :registry (commando/registry-create {:test/failing (:timeout-cmd failing-commands)}) :internal/cm-running-order [(cm/->CommandMapPath ["cmd"] (:timeout-cmd failing-commands))]}) (def bad-command-execution-map {:status :ok :instruction {"bad" {:will-fail true}} - :registry (commando/create-registry [(:bad-cmd failing-commands)]) + :registry (commando/registry-create {:test/bad (:bad-cmd failing-commands)}) :internal/cm-running-order [(cm/->CommandMapPath ["bad"] (:bad-cmd failing-commands))]}) (def midway-fail-execution-map @@ -729,7 +634,8 @@ :instruction {"good" {:test/add-id "works"} "bad" {:will-fail true} "never" {:test/add-id "should-not-execute"}} - :registry (commando/create-registry [test-add-id-command (:bad-cmd failing-commands)]) + :registry (commando/registry-create {:test/add-id test-add-id-command + :test/bad (:bad-cmd failing-commands)}) :internal/cm-running-order [(cm/->CommandMapPath ["good"] test-add-id-command) (cm/->CommandMapPath ["bad"] (:bad-cmd failing-commands)) (cm/->CommandMapPath ["never"] test-add-id-command)]}) @@ -743,13 +649,13 @@ (def nil-handler-execution-map {:status :ok :instruction {"nil-handler" {:handle-nil nil}} - :registry (commando/create-registry [nil-handler-command]) + :registry (commando/registry-create {:test/nil-handler nil-handler-command}) :internal/cm-running-order [(cm/->CommandMapPath ["nil-handler"] nil-handler-command)]}) (def deep-nested-execution-map {:status :ok :instruction {"level1" {"level2" {"level3" {"deep" {:test/add-id "deep-value"}}}}} - :registry (commando/create-registry [test-add-id-command]) + :registry (commando/registry-create {:test/add-id test-add-id-command}) :internal/cm-running-order [(cm/->CommandMapPath ["level1" "level2" "level3" "deep"] test-add-id-command)]}) (def large-commands-execution-map @@ -757,16 +663,16 @@ instruction (into {} (map #(vector % {:test/add-id (str "value-" %)}) (range 20)))] {:status :ok :instruction instruction - :registry (commando/create-registry [test-add-id-command]) + :registry (commando/registry-create {:test/add-id test-add-id-command}) :internal/cm-running-order commands})) (def full-registry-all - [cmds-builtin/command-from-spec - cmds-builtin/command-fn-spec - cmds-builtin/command-apply-spec - cmds-builtin/command-mutation-spec - cmds-builtin/command-macro-spec - test-add-id-command]) + {:commando/from cmds-builtin/command-from-spec + :commando/fn cmds-builtin/command-fn-spec + :commando/apply cmds-builtin/command-apply-spec + :commando/mutation cmds-builtin/command-mutation-spec + :commando/macro cmds-builtin/command-macro-spec + :test/add-id test-add-id-command}) (def from-instruction {"a" 10 @@ -818,7 +724,9 @@ "child2" {:commando/from ["parent" "child1"]}}}) ;; Error scenarios data and registries -(def error-registry [cmds-builtin/command-from-spec test-add-id-command (:timeout-cmd failing-commands)]) +(def error-registry {:commando/from cmds-builtin/command-from-spec + :test/add-id test-add-id-command + :test/failing (:timeout-cmd failing-commands)}) (def invalid-cmd {:type :test/invalid @@ -885,7 +793,7 @@ :dependencies {:mode :point :point-key [:ARG]}}) -(def custom-registry [custom-op-cmd custom-arg-cmd]) +(def custom-registry {:OP custom-op-cmd :ARG custom-arg-cmd}) ;; Helper-integration instructions (def value-ref-instruction @@ -949,12 +857,13 @@ 1]}}) ;; Test data for execute-function-comprehensive-test -(def registry-from-spec [cmds-builtin/command-from-spec]) +(def registry-from-spec {:commando/from cmds-builtin/command-from-spec}) (def test-instruction {"source" 42 "ref" {:commando/from ["source"]}}) -(def basic-from-registry [cmds-builtin/command-from-spec test-add-id-command]) +(def basic-from-registry {:commando/from cmds-builtin/command-from-spec + :test/add-id test-add-id-command}) (def nested-instruction {"level1" {"level2" {"cmd" {:test/add-id "deep"}}}}) (def vector-instruction {"items" [{:test/add-id "first"} {:test/add-id "second"}]}) (def mixed-keys-instruction @@ -1014,70 +923,6 @@ (is (= nil (get-in (#'commando/execute-commands! nil-handler-execution-map) [:instruction "nil-handler"])) "Nil values handled correctly"))) -(def build-compiler-test-data - {:valid-registry [cmds-builtin/command-from-spec] - :valid-instruction {"a" 1 - "b" {:commando/from ["a"]}} - :cyclic-instruction {"a" {:commando/from ["b"]} - "b" {:commando/from ["a"]}} - :malformed-registry [{:type :broken - :recognize-fn "not-a-function"}] - :basic-registry [cmds-builtin/command-from-spec test-add-id-command] - :basic-instruction {"cmd" {:test/add-id "value"} - "ref" {:commando/from ["cmd"]}} - :invalid-ref-instruction {"ref" {:commando/from ["nonexistent"]}} - :cmd-instruction {"cmd" {:test/add-id "value"}} - :large-instruction (into {} (map #(vector (str %) {:test/add-id %}) (range 50)))}) - -(deftest build-compiler-test - (testing "Status handling" - (is (= :ok - (:status (commando/build-compiler (:valid-registry build-compiler-test-data) - (:valid-instruction build-compiler-test-data)))) - "Returns :ok status for valid registry and instruction") - (is (helpers/status-map-contains-error? (commando/build-compiler - (:valid-registry build-compiler-test-data) - (:cyclic-instruction build-compiler-test-data)) - "Commando. sort-entities-by-deps. Detected cyclic dependency") - "Returns :failed status for cyclic dependencies") - (is (helpers/status-map-contains-error? (commando/build-compiler - (:malformed-registry build-compiler-test-data) - (:valid-instruction build-compiler-test-data)) - "Invalid registry specification") - "Returns :failed status for malformed registry")) - (testing "Basic functionality" - (let [compiler (commando/build-compiler (:basic-registry build-compiler-test-data) - (:basic-instruction build-compiler-test-data))] - (is (= :ok (:status compiler)) "Compiler contain :status == :ok") - (is (not-empty (:registry compiler)) "Compiler contain :registry") - (is (= [(cm/->CommandMapPath ["cmd"] {:type :test/add-id}) (cm/->CommandMapPath ["ref"] {:type :commando/from})] - (:internal/cm-running-order compiler)) - "Compiler contain :internal/cm-running-order"))) - (testing "Error scenarios" - (is (= :failed - (:status (commando/build-compiler [cmds-builtin/command-from-spec] - (:invalid-ref-instruction build-compiler-test-data)))) - "Invalid reference causes failure") - (is (helpers/status-map-contains-error? - (commando/build-compiler [cmds-builtin/command-from-spec] (:invalid-ref-instruction build-compiler-test-data)) - "Commando. Point dependency failed: key ':commando/from' references non-existent path [\"nonexistent\"]") - "Error information is populated") - (is (helpers/status-map-contains-error? (commando/build-compiler [] (:cmd-instruction build-compiler-test-data)) - "Invalid registry specification") - "Error cause the empty registry")) - (testing "Edge cases" - (is (commando/ok? (commando/build-compiler [test-add-id-command] {"data" "no-commands"})) - "Registry with no matching commands") - (is (helpers/status-map-contains-error? (commando/build-compiler - (repeat 5 test-add-id-command) - (:large-instruction build-compiler-test-data)) - "Invalid registry specification") - "duplicate commands in registry cause an error") - (is (= 50 - (count (:internal/cm-running-order (commando/build-compiler [test-add-id-command] - (:large-instruction build-compiler-test-data))))) - "All commands processed"))) - (def relative-path-instruction {"1" 1 "2" {"container" {:commando/from ["../" "../" "1"]}} @@ -1103,61 +948,39 @@ {:commando/from [1] := (partial * 2)}]) - -(def compiler (commando/build-compiler full-registry-all base-instruction-compiler)) - (deftest execute-test (testing "Status" (is (commando/ok? (commando/execute registry-from-spec test-instruction)) "Status :ok when successful") - (is (= :ok - (:status (commando/execute (commando/build-compiler registry-from-spec test-instruction) test-instruction))) - "Pre-compiled compiler usage also returns :ok when successful") - (is (= (:status (commando/execute registry-from-spec test-instruction)) - (:status (commando/execute (commando/build-compiler registry-from-spec test-instruction) test-instruction))) - "Registry and compiler produce identical status results") (is (= :ok (:status (commando/execute basic-from-registry {"data" 123 "info" "text"}))) "Instruction with no commands succeeds") (is (commando/ok? (commando/execute basic-from-registry mixed-keys-instruction)) "Mixed data types as keys succeed") - (is (commando/failed? (commando/execute [] empty-registry-instruction))) + (is (commando/failed? (commando/execute {} empty-registry-instruction))) (is (commando/failed? (commando/execute error-registry failing-case-instruction))) - (is (commando/failed? (commando/execute [cmds-builtin/command-from-spec] invalid-ref-instruction))) - (is (not-empty (:errors (commando/execute [cmds-builtin/command-from-spec] invalid-ref-instruction)))) - (is (commando/failed? (commando/execute [cmds-builtin/command-from-spec] circular-instruction)) + (is (commando/failed? (commando/execute {:commando/from cmds-builtin/command-from-spec} invalid-ref-instruction))) + (is (not-empty (:errors (commando/execute {:commando/from cmds-builtin/command-from-spec} invalid-ref-instruction)))) + (is (commando/failed? (commando/execute {:commando/from cmds-builtin/command-from-spec} circular-instruction)) "Circular dependencies") - (is (commando/failed? (commando/execute [invalid-cmd] invalid-validation-instruction)) "Invalid command validation") - (is (commando/failed? (commando/execute [throwing-cmd] throwing-recognition-instruction)) + (is (commando/failed? (commando/execute {:test/invalid invalid-cmd} invalid-validation-instruction)) "Invalid command validation") + (is (commando/failed? (commando/execute {:test/throwing throwing-cmd} throwing-recognition-instruction)) "Command recognition exception") - (let [result (commando/execute [cmds-builtin/command-from-spec] unexisting-path-instruction)] + (let [result (commando/execute {:commando/from cmds-builtin/command-from-spec} unexisting-path-instruction)] (is (commando/failed? result)) (is (= (:errors result) [{:message "Commando. Point dependency failed: key ':commando/from' references non-existent path [\"UNEXISTING_PATH\"]", :path ["2" :container], - :command {:commando/from ["UNEXISTING_PATH"]}} - {:message "Corrupted compiler structure"}]))) - (is (commando/failed? (commando/execute [cmds-builtin/command-apply-spec] {"plain" {:commando/apply [1 2 3]}})) + :command {:commando/from ["UNEXISTING_PATH"]}}]))) + (is (commando/failed? (commando/execute {:commando/apply cmds-builtin/command-apply-spec} {"plain" {:commando/apply [1 2 3]}})) "Missing := parameter causes validation failure")) (testing "Basic cases" (is (= 42 (get-in (commando/execute registry-from-spec test-instruction) [:instruction "ref"])) "Command executed correctly") (is (= 42 (get-in (commando/execute registry-from-spec test-instruction) [:instruction "source"])) "Static value preserved") - (is (= 42 - (get-in (commando/execute (commando/build-compiler registry-from-spec test-instruction) test-instruction) - [:instruction "ref"])) - "Pre-compiled execution works correctly") - (is (= 42 - (get-in (commando/execute (commando/build-compiler registry-from-spec test-instruction) test-instruction) - [:instruction "source"])) - "Pre-compiled compiler static value preserved") - (is (= (:instruction (commando/execute registry-from-spec test-instruction)) - (:instruction (commando/execute (commando/build-compiler registry-from-spec test-instruction) - test-instruction))) - "Registry and compiler produce identical instruction results") (is (commando/ok? (commando/execute basic-from-registry {})) "Empty instruction succeeds") (is (= {} (:instruction (commando/execute basic-from-registry {}))) "Empty instruction preserves input instruction map") @@ -1191,7 +1014,7 @@ [:instruction 0 1 2 3 4 5 6 7 8 9 "cmd"]) :id) "Deep nested command executes") - (is (= {"cmd" {:test/add-id "value"}} (:instruction (commando/execute [] empty-registry-instruction))) + (is (= {"cmd" {:test/add-id "value"}} (:instruction (commando/execute {} empty-registry-instruction))) "empty registry preserves instruction") (is (= 200 (count (filter #(contains? % :id) @@ -1204,7 +1027,7 @@ :id) (range 100))) (is (= 5 - (get-in (commando/execute [cmds-builtin/command-from-spec] sum-collection-instruction) [:instruction "0"]))) + (get-in (commando/execute {:commando/from cmds-builtin/command-from-spec} sum-collection-instruction) [:instruction "0"]))) (is (= {"A" 5 "B" 10 "result-multiply-1" 20 @@ -1222,11 +1045,11 @@ := #(get % "3")}} := #(get % "2")}} := #(get % "1")}} - (commando/execute [cmds-builtin/command-apply-spec]) + (commando/execute {:commando/apply cmds-builtin/command-apply-spec}) :instruction)) "Commands inside commands are executed correctly") (is (= "john" - (get-in (:instruction (commando/execute [cmds-builtin/command-from-spec] + (get-in (:instruction (commando/execute {:commando/from cmds-builtin/command-from-spec} {"source" {:user-name "john" :age 25} "name" {:commando/from ["source"] @@ -1234,34 +1057,34 @@ ["name"])) "Value extracted correctly with := in commando/from using keyword") (is (= 25 - (get-in (:instruction (commando/execute [cmds-builtin/command-from-spec] + (get-in (:instruction (commando/execute {:commando/from cmds-builtin/command-from-spec} {"source" {"age" 25} "age" {:commando/from ["source"] := "age"}})) ["age"])) "Value extracted correctly with := in commando/from using string") (is (= 15 - (get-in (:instruction (commando/execute [cmds-builtin/command-from-spec] + (get-in (:instruction (commando/execute {:commando/from cmds-builtin/command-from-spec} {"numbers" [1 2 3 4 5] "sum" {:commando/from ["numbers"] := #(reduce + %)}})) ["sum"])) "commando/from := syntax applying function works") (is (= 1 - (get-in (:instruction (commando/execute [cmds-builtin/command-from-spec] + (get-in (:instruction (commando/execute {:commando/from cmds-builtin/command-from-spec} {"numbers" [1 2 3 4 5] "first" {:commando/from ["numbers"] := first}})) ["first"])) "commando/from := syntax applying function works") - (is (nil? (get-in (:instruction (commando/execute [cmds-builtin/command-from-spec] + (is (nil? (get-in (:instruction (commando/execute {:commando/from cmds-builtin/command-from-spec} {"source" {:a 1 :b 2} "missing" {:commando/from ["source"] := :nonexistent}})) ["missing"])) "commando/from := nil returned when value is missing") - (is (nil? (get-in (:instruction (commando/execute [cmds-builtin/command-from-spec] + (is (nil? (get-in (:instruction (commando/execute {:commando/from cmds-builtin/command-from-spec} {"source" {:a 1 :b 2} "missing" {:commando/from ["source"] @@ -1269,18 +1092,19 @@ ["missing"])) "commando/from := nil returned when value is missing") (is (= '(20 40 60) - (get-in (:instruction (commando/execute [cmds-builtin/command-apply-spec cmds-builtin/command-from-spec] + (get-in (:instruction (commando/execute {:commando/apply cmds-builtin/command-apply-spec + :commando/from cmds-builtin/command-from-spec} {"base" [10 20 30] "doubled" {:commando/apply {:commando/from ["base"]} := #(map (partial * 2) %)}})) ["doubled"])) "commando/apply works with just a command as a value") (testing "Single command types" - (is (= 10 (get-in (commando/execute [cmds-builtin/command-from-spec] from-instruction) [:instruction "b"]))) - (is (= 6 (get-in (commando/execute [cmds-builtin/command-fn-spec] fn-instruction) [:instruction "calc"]))) + (is (= 10 (get-in (commando/execute {:commando/from cmds-builtin/command-from-spec} from-instruction) [:instruction "b"]))) + (is (= 6 (get-in (commando/execute {:commando/fn cmds-builtin/command-fn-spec} fn-instruction) [:instruction "calc"]))) (is - (= 3 (get-in (commando/execute [cmds-builtin/command-apply-spec] apply-instruction) [:instruction "transform"]))) - (is (contains? (get-in (commando/execute [test-add-id-command] add-id-test-instruction) [:instruction "cmd"]) + (= 3 (get-in (commando/execute {:commando/apply cmds-builtin/command-apply-spec} apply-instruction) [:instruction "transform"]))) + (is (contains? (get-in (commando/execute {:test/add-id test-add-id-command} add-id-test-instruction) [:instruction "cmd"]) :id))) (testing "Mixed command types in single instruction" (is (= 100 (get-in (commando/execute full-registry-all mixed-instruction) [:instruction "source"]))) @@ -1316,37 +1140,32 @@ [:instruction "parent" "child2"]) :id))) (testing ":point dependency lookup in set/list cause a failure" - (is (and (commando/ok? (commando/execute [cmds-builtin/command-from-spec] structure-map-instruction)) + (is (and (commando/ok? (commando/execute {:commando/from cmds-builtin/command-from-spec} structure-map-instruction)) (= 1 - (get-in (commando/execute [cmds-builtin/command-from-spec] structure-map-instruction) + (get-in (commando/execute {:commando/from cmds-builtin/command-from-spec} structure-map-instruction) [:instruction "="])))) - (is (and (commando/ok? (commando/execute [cmds-builtin/command-from-spec] structure-vector-instruction)) + (is (and (commando/ok? (commando/execute {:commando/from cmds-builtin/command-from-spec} structure-vector-instruction)) (= 1 - (get-in (commando/execute [cmds-builtin/command-from-spec] structure-vector-instruction) + (get-in (commando/execute {:commando/from cmds-builtin/command-from-spec} structure-vector-instruction) [:instruction "="])))) - (is (commando/failed? (commando/execute [cmds-builtin/command-from-spec] structure-set-instruction))) - (is (commando/failed? (commando/execute [cmds-builtin/command-from-spec] structure-list-instruction)))) + (is (commando/failed? (commando/execute {:commando/from cmds-builtin/command-from-spec} structure-set-instruction))) + (is (commando/failed? (commando/execute {:commando/from cmds-builtin/command-from-spec} structure-list-instruction)))) (testing "Navigation with relative path ../" (is (= 1 - (get-in (:instruction (commando/execute [cmds-builtin/command-from-spec] relative-path-instruction)) + (get-in (:instruction (commando/execute {:commando/from cmds-builtin/command-from-spec} relative-path-instruction)) ["2" "container"])) "Parent path resolves to correct value") (is (= {"container" 1} - (get-in (:instruction (commando/execute [cmds-builtin/command-from-spec] relative-path-instruction)) + (get-in (:instruction (commando/execute {:commando/from cmds-builtin/command-from-spec} relative-path-instruction)) ["3" "container"])) "Nested parent path with transformation works")) (testing "Top-level Vector Instruction" - (let [result (commando/execute [cmds-builtin/command-from-spec] toplevel-vector-instruction)] + (let [result (commando/execute {:commando/from cmds-builtin/command-from-spec} toplevel-vector-instruction)] (is (commando/ok? result) "This type of instruction is also acceptable") (is (= [{:value 10} 11 22] (:instruction result)) "Result of toplevel-vector instruction not match with example"))) - (testing "Compiler reuse optimization" - (let [result1 (commando/execute compiler base-instruction-compiler) - modified-instruction (assoc base-instruction-compiler "1" 1000) - result2 (commando/execute compiler modified-instruction)] - (is (commando/ok? compiler) "Compiler builds successfully") - (is (commando/ok? result1) "Original execution succeeds") - (is (commando/ok? result2) "Modified execution succeeds") - (is (= 3 (get-in (:instruction result1) ["0"])) "Original calculation correct") - (is (= 3000 (get-in (:instruction result2) ["0"])) "Modified calculation correct"))))) - + (testing "Execute with built registry" + (let [built-reg (commando/registry-create full-registry-all) + result1 (commando/execute built-reg base-instruction-compiler)] + (is (commando/ok? result1) "Built registry execution succeeds") + (is (= 3 (get-in (:instruction result1) ["0"])) "Calculation correct with built registry"))))) diff --git a/test/unit/commando/impl/registry_test.clj b/test/unit/commando/impl/registry_test.clj new file mode 100644 index 0000000..595d9be --- /dev/null +++ b/test/unit/commando/impl/registry_test.clj @@ -0,0 +1,96 @@ +(ns commando.impl.registry-test + (:require + [clojure.test :refer [deftest is testing]] + [clojure.string :as string] + [commando.core :as commando] + [commando.commands.builtin :as cmds-builtin] + [commando.impl.registry :as registry])) + +(def custom-spec + {:type :custom/upper + :recognize-fn #(and (map? %) (contains? % :custom/upper)) + :apply (fn [_instruction _command m] + (string/upper-case (:custom/upper m))) + :dependencies {:mode :none}}) + +(deftest build-registry-from-map + (testing "Build registry from a map of specs" + (let [r (registry/build {:commando/from cmds-builtin/command-from-spec + :commando/fn cmds-builtin/command-fn-spec})] + (is (registry/built? r)) + (is (= #{:commando/from :commando/fn} (set (keys (:registry r))))) + (is (= 2 (count (:registry-order r))))))) + +(deftest build-registry-from-map-with-order + (testing "Build registry from a map with explicit order" + (let [r (registry/build + {:commando/from cmds-builtin/command-from-spec + :commando/fn cmds-builtin/command-fn-spec} + {:registry-order [:commando/fn :commando/from]})] + (is (= [:commando/fn :commando/from] (:registry-order r)))))) + +(deftest registry-create-from-vector + (testing "registry-create accepts a vector and preserves order" + (let [r (commando/registry-create + [cmds-builtin/command-fn-spec + cmds-builtin/command-from-spec])] + (is (registry/built? r)) + (is (= [:commando/fn :commando/from] (:registry-order r))) + (is (= #{:commando/fn :commando/from} (set (keys (:registry r)))))))) + +(deftest registry-create-idempotent + (testing "Passing an already-built registry returns it unchanged" + (let [r (commando/registry-create [cmds-builtin/command-from-spec]) + r2 (commando/registry-create r)] + (is (identical? r r2))))) + +(deftest registry-assoc-adds-spec + (testing "registry-assoc adds a new spec to a built registry" + (let [r (commando/registry-create {:commando/from cmds-builtin/command-from-spec}) + r2 (commando/registry-assoc r :custom/upper custom-spec)] + (is (registry/built? r2)) + (is (contains? (:registry r2) :custom/upper)) + (is (some #{:custom/upper} (:registry-order r2))) + (is (contains? (:registry r2) :commando/from))))) + +(deftest registry-assoc-replaces-spec + (testing "registry-assoc replaces an existing spec" + (let [r (commando/registry-create {:commando/from cmds-builtin/command-from-spec + :custom/upper custom-spec}) + new-spec (assoc custom-spec :apply (fn [_ _ m] (string/lower-case (:custom/upper m)))) + r2 (commando/registry-assoc r :custom/upper new-spec)] + (is (= (get-in r2 [:registry :custom/upper :apply]) + (:apply new-spec)))))) + +(deftest registry-dissoc-removes-spec + (testing "registry-dissoc removes a spec from registry" + (let [r (commando/registry-create {:commando/from cmds-builtin/command-from-spec + :custom/upper custom-spec}) + r2 (commando/registry-dissoc r :custom/upper)] + (is (registry/built? r2)) + (is (not (contains? (:registry r2) :custom/upper))) + (is (not (some #{:custom/upper} (:registry-order r2)))) + (is (contains? (:registry r2) :commando/from))))) + +(deftest registry-execute-with-vector + (testing "Execute works when registry was created from a vector" + (let [result (commando/execute + [cmds-builtin/command-from-spec] + {"a" 1 "b" {:commando/from ["a"]}})] + (is (commando/ok? result)) + (is (= {"a" 1 "b" 1} (:instruction result)))))) + +(deftest registry-execute-with-map + (testing "Execute works when registry was created from a map" + (let [result (commando/execute + {:commando/from cmds-builtin/command-from-spec} + {"a" 1 "b" {:commando/from ["a"]}})] + (is (commando/ok? result)) + (is (= {"a" 1 "b" 1} (:instruction result)))))) + +(deftest registry-execute-with-built + (testing "Execute works when registry was pre-built" + (let [r (commando/registry-create [cmds-builtin/command-from-spec]) + result (commando/execute r {"a" 1 "b" {:commando/from ["a"]}})] + (is (commando/ok? result)) + (is (= {"a" 1 "b" 1} (:instruction result))))))