Welcome! Please see the About page for a little more info on how this works.

0 votes
in Spec by

I have the following data structure as the state of my mini restaurant app

    {:orders {:e4d55743-c964-48e8-9cd1-cb1cf9075617 [#:helloworld.restaurant{:name "chilly parotta", :quantity 3}  #:helloworld.restaurant{:name "masal dosa", :quantity 2}]}, :menu {:kothu-parotta #:helloworld.restaurant{:name "Kothu Parotta", :price 9.5, :quantity 50},  :butter-naan #:helloworld.restaurant{:name "Butter Naan", :price 3, :quantity 50}, :paneer-butter-masala #:helloworld.restaurant{:name "Paneer Butter Masala", :price 9.5, :quantity 50}}}

I have a function to update the menu item corresponding to every order item in the order to deduct the order quantity from the inventory and return the new menu. I want to validate

  1. The item in the order item, should exist in the menu
  2. The item's quantity in the inventory, should be greater than the ordered quantity

I have the following specs (Note: I have not validated 2 for now). When I run this, function call to update-menu-item-for-order-item is not validating my predicate for 1. What am I doing wrong in the fdef spec?

(s/def ::name string?)
(s/def ::price number?)
(s/def ::quantity number?)
(s/def ::order-item (s/keys :req [::name ::quantity]))
(s/def ::order-items (s/coll-of ::order-item))
(s/def ::menu-item (s/keys :req [::name ::price ::quantity]))
(s/def ::menu-item-id keyword?)
(s/def ::menu (s/map-of ::menu-item-id ::menu-item))

(s/fdef restaurant-save-menu :args (s/cat :menu-items (s/coll-of ::menu-item)))

(s/fdef update-menu-item-for-order-item
        :args (s/and (s/cat :menu ::menu :order-item ::order-item)
                     #(contains? (:menu %) (keyword (slugify (::name (:order-item %))))))
        :ret ::menu)

(stest/instrument `update-menu-item-for-order-item)
(stest/instrument `restaurant-save-menu)

1 Answer

0 votes
selected by
Best answer

What call are you making? What result do you see?

Another debugging technique is to test just the args spec for the function independent from the function (you can separate it and name it to make that easier but it's also retrievable from the function spec):

    (s/valid? (:args (s/get-spec `update-menu-item-for-order-item)) [a-menu a-item])
Here is the code. When I call restaurant-create-order, it calls create-order, which in turn calls update-menu-item-for-order-item. Somehow, now the assertion 1 (the item ordered should exist in the menu works fine). Earlier, it wasn't working and I am not sure what I did, and now it works! Thanks anyways.

(def restaurant (atom {}))

(defn update-menu-item-for-order-item [menu order-item]
  (let [name (::name order-item)
        item-id (keyword (slugify (::name order-item)))
        order-quantity (::quantity order-item)]
    (update-in menu
               (fn [item]
                 (if (nil? item)
                   (throw (IllegalArgumentException. (str "Sorry, we don't have " name " in our menu")))
                   (if (< (::quantity item) order-quantity)
                     (throw (IllegalArgumentException.
                              (str "Not enough " (::name item) ", we are short by "
                                   (- order-quantity (::quantity item))
                                   " . Please correct the order and re-submit")))
                     (assoc item ::quantity (- (::quantity item) order-quantity))))))))

(defn create-order [orders menu order-items]
  (let [order-id (keyword (str (UUID/randomUUID)))]
    {::orders (assoc orders order-id order-items)
     ::menu   (reduce #(update-menu-item-for-order-item %1 %2) menu order-items)}))

(defn restaurant-create-order [order-items]
  (swap! restaurant #(create-order (::orders %) (::menu %) order-items)))

(restaurant-create-order [{::name "chilly parotta" ::quantity 3}
                          {::name "masal dosa" ::quantity 2}])
I don't have time to read all this code - can you just boil it down to a single invocation of a function with literal data?
The code works for both me and Prabu at this point. I suspect it was stale REPL state.
Yes, Thanks @alexmiller. The code works now