diff --git a/src/nl/surf/eduhub_rio_mapper/cli_commands.clj b/src/nl/surf/eduhub_rio_mapper/cli_commands.clj index 82a4465d..e7e67f10 100644 --- a/src/nl/surf/eduhub_rio_mapper/cli_commands.clj +++ b/src/nl/surf/eduhub_rio_mapper/cli_commands.clj @@ -29,6 +29,8 @@ [nl.surf.eduhub-rio-mapper.rio.loader :as rio.loader] [nl.surf.eduhub-rio-mapper.specs.ooapi :as ooapi] [nl.surf.eduhub-rio-mapper.specs.rio :as rio] + [nl.surf.eduhub-rio-mapper.utils.http-utils :refer [*http-messages*]] + [nl.surf.eduhub-rio-mapper.utils.printer :as printer] [nl.surf.eduhub-rio-mapper.worker :as worker]) (:import [java.util UUID])) @@ -76,44 +78,47 @@ "test-rio" (let [[client-info _args] (parse-client-info-args args clients) - uuid (UUID/randomUUID) - new-uuid (UUID/randomUUID) + old-uuid (UUID/randomUUID) + new-uuid (UUID/randomUUID) eduspec (-> "../test/fixtures/ooapi/education-specification-template.json" io/resource slurp (json/read-str :key-fn keyword) - (assoc :educationSpecificationId uuid))] - - (try - (let [insert-req {:institution-oin (:institution-oin client-info) - :institution-schac-home (:institution-schac-home client-info) - ::ooapi/type "education-specification" - ::ooapi/id uuid - ::ooapi/entity eduspec} - rio-code (-> insert-req insert! :aanleveren_opleidingseenheid_response :opleidingseenheidcode) - link-req (merge insert-req {::ooapi/id new-uuid ::rio/opleidingscode rio-code})] - (link! link-req) - (let [rio-obj (rio.loader/find-rio-object rio-code getter (:institution-oin client-info) "opleidingseenheid") - nieuwe-sleutel (->> rio-obj - :content - (filter #(= :kenmerken (:tag %))) - (map :content) - (map #(reduce (fn [m el] (assoc m (:tag el) (-> el :content first))) {} %)) - (filter #(= "eigenOpleidingseenheidSleutel" (:kenmerknaam %))) - first - :kenmerkwaardeTekst)] - (when (not= nieuwe-sleutel (str uuid)) - (throw (ex-info "Failed to set eigenOpleidingseenheidSleutel" {:rio-queue-status :down}))))) - - (println "The RIO Queue is UP") - (catch Exception ex - (when-let [ex-data (ex-data ex)] - (when (= :down (:rio-queue-status ex-data)) - (println "The RIO Queue is DOWN;" (.getMessage ex)) - (System/exit 255))) - (println "An unexpected exception has occurred: " ex) - (System/exit 254)))) + (assoc :educationSpecificationId old-uuid))] + + (binding [*http-messages* (atom [])] + (try + (let [insert-req {:institution-oin (:institution-oin client-info) + :institution-schac-home (:institution-schac-home client-info) + ::ooapi/type "education-specification" + ::ooapi/id old-uuid + ::ooapi/entity eduspec} + rio-code (-> insert-req insert! :aanleveren_opleidingseenheid_response :opleidingseenheidcode) + link-req (merge insert-req {::ooapi/id new-uuid ::rio/opleidingscode rio-code})] + (link! link-req) + (let [rio-obj (rio.loader/find-rio-object rio-code getter (:institution-oin client-info) "opleidingseenheid") + nieuwe-sleutel (->> rio-obj + :content + (filter #(= :kenmerken (:tag %))) + (map :content) + (map #(reduce (fn [m el] (assoc m (:tag el) (-> el :content first))) {} %)) + (filter #(= "eigenOpleidingseenheidSleutel" (:kenmerknaam %))) + first + :kenmerkwaardeTekst)] + (when (not= nieuwe-sleutel (str new-uuid)) + (println "old uuid " old-uuid) + (println "new uuid " new-uuid) + (throw (ex-info "Failed to set eigenOpleidingseenheidSleutel" {:rio-queue-status :down})))) + (println "The RIO Queue is UP")) + (catch Exception ex + (when-let [ex-data (ex-data ex)] + (when (= :down (:rio-queue-status ex-data)) + (println "The RIO Queue is DOWN;" (.getMessage ex)) + (printer/print-http-messages @*http-messages*) + (System/exit 255))) + (println "An unexpected exception has occurred: " ex) + (System/exit 254))))) "get" (let [[client-info rest-args] (parse-client-info-args args clients)] diff --git a/src/nl/surf/eduhub_rio_mapper/utils/printer.clj b/src/nl/surf/eduhub_rio_mapper/utils/printer.clj new file mode 100644 index 00000000..e11b79b3 --- /dev/null +++ b/src/nl/surf/eduhub_rio_mapper/utils/printer.clj @@ -0,0 +1,121 @@ +;; This file is part of eduhub-rio-mapper +;; +;; Copyright (C) 2022 SURFnet B.V. +;; +;; This program is free software: you can redistribute it and/or +;; modify it under the terms of the GNU Affero General Public License +;; as published by the Free Software Foundation, either version 3 of +;; the License, or (at your option) any later version. +;; +;; This program is distributed in the hope that it will be useful, but +;; WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +;; Affero General Public License for more details. +;; +;; You should have received a copy of the GNU Affero General Public +;; License along with this program. If not, see +;; . + +(ns nl.surf.eduhub-rio-mapper.utils.printer + (:require [clojure.data.json :as json] + [clojure.string :as str] + [clojure.xml :as xml] + [nl.jomco.http-status-codes :as http-status] + [nl.surf.eduhub-rio-mapper.utils.xml-utils :as xml-utils]) + (:import [java.io ByteArrayInputStream StringWriter])) + +(defmacro print-boxed + "Print pretty box around output of evaluating `form`." + [title & form] + `(let [sw# (StringWriter.) + r# (binding [*out* sw#] ~@form) + s# (str sw#)] + (println) + (print "╭─────" ~title "\n│ ") + (println (str/replace (str/trim s#) #"\n" "\n│ ")) + (println "╰─────") + r#)) + +(defn- print-soap-body + "Print the body of a SOAP request or response." + [s] + ;; Use clojure.xml/parse because it is more lenient than + ;; clojure.data.xml/parse which trips over missing namespaces. + (let [xml (xml/parse (ByteArrayInputStream. (.getBytes s)))] + (xml-utils/debug-print-xml (-> xml :content second :content first) + :initial-indent " "))) + +(defn print-json + "Print indented JSON." + [v] + (when v + (print " ") + (println (json/write-str v :indent true :indent-depth 1)))) + +(defn- print-json-str + "Parse string as JSON and print it." + [s] + (when s + (print-json (json/read-str s)))) + +(defn- print-rio-message + "Print boxed RIO request and response." + [{{:keys [method url] + req-body :body + {action :SOAPAction} :headers} :req + {res-body :body + :keys [status]} :res}] + (println (str/upper-case (name method)) url status) + (println "- action:" action) + (println "- request:\n") + (print-soap-body req-body) + (println) + (when (= http-status/ok status) + (println "- response:\n") + (print-soap-body res-body) + (println))) + +(defn- print-ooapi-message + "Print boxed OOAPI request and response." + [{{:keys [method url]} :req + {:keys [status body]} :res}] + (println (str/upper-case method) url status) + (println) + (when (= http-status/ok status) + (print-json-str body))) + +(defn- keywordize-keys + "Recursively change map keys to keywords." + [m] + (->> m + (map (fn [[k v]] + [(if (keyword? k) + k + (keyword k)) + (if (map? v) + (keywordize-keys v) + v)])) + (into {}))) + +(defn print-http-messages-with-boxed-printer + "Print HTTP message as returned by API status." + [http-messages print-boxed-fn] + (when-let [msg (first http-messages)] + ;; need to keywordize-keys because http-message may be translated + ;; from from JSON (in which case they are all keywords) or come + ;; strait from http-utils (which is a mixed bag) + (let [{:keys [req] :as msg} (keywordize-keys msg) + soap? (-> req :headers :SOAPAction) + title (if soap? "RIO" "OOAPI") + print-fn (if soap? print-rio-message + print-ooapi-message)] + (print-boxed-fn title print-fn msg)) + (recur (next http-messages) print-boxed-fn))) + +(defn print-single-http-message [title print-fn msg] + (print-boxed title (print-fn msg))) + +(defn print-http-messages + "Print HTTP message as returned by API status." + [http-messages] + (print-http-messages-with-boxed-printer http-messages print-single-http-message)) diff --git a/test/nl/surf/eduhub_rio_mapper/e2e_helper.clj b/test/nl/surf/eduhub_rio_mapper/e2e_helper.clj index d6977537..4562d682 100644 --- a/test/nl/surf/eduhub_rio_mapper/e2e_helper.clj +++ b/test/nl/surf/eduhub_rio_mapper/e2e_helper.clj @@ -18,10 +18,8 @@ (ns nl.surf.eduhub-rio-mapper.e2e-helper (:require [clj-http.client :as http] - [clojure.data.json :as json] [clojure.string :as str] [clojure.test :as test] - [clojure.xml :as xml] [environ.core :refer [env]] [nl.jomco.http-status-codes :as http-status] [nl.surf.eduhub-rio-mapper.clients-info :as clients-info] @@ -31,8 +29,9 @@ [nl.surf.eduhub-rio-mapper.rio.loader :as rio-loader] [nl.surf.eduhub-rio-mapper.specs.rio :as rio] [nl.surf.eduhub-rio-mapper.utils.http-utils :as http-utils] + [nl.surf.eduhub-rio-mapper.utils.printer :as printer] [nl.surf.eduhub-rio-mapper.utils.xml-utils :as xml-utils]) - (:import [java.io ByteArrayInputStream StringWriter] + (:import [java.io StringWriter] [java.net ConnectException] [java.util Base64] [javax.xml.xpath XPathFactory] @@ -71,90 +70,21 @@ (reset! last-boxed-print s#))) r#)) -(defn- print-soap-body - "Print the body of a SOAP request or response." - [s] - ;; Use clojure.xml/parse because it is more lenient than - ;; clojure.data.xml/parse which trips over missing namespaces. - (let [xml (xml/parse (ByteArrayInputStream. (.getBytes s)))] - (xml-utils/debug-print-xml (-> xml :content second :content first) - :initial-indent " "))) - -(defn- print-json - "Print indented JSON." - [v] - (when v - (print " ") - (println (json/write-str v :indent true :indent-depth 1)))) - -(defn- print-json-str - "Parse string as JSON and print it." - [s] - (when s - (print-json (json/read-str s)))) - (defn- print-api-message "Print boxed API request and response." [{{:keys [method url]} :req {:keys [status body]} :res}] - (print-boxed "API" - (println (str/upper-case (name method)) url status) - (when body - (print-json body)))) - -(defn- print-rio-message - "Print boxed RIO request and response." - [{{:keys [method url] - req-body :body - {action :SOAPAction} :headers} :req - {res-body :body - :keys [status]} :res}] - (print-boxed "RIO" - (println (str/upper-case (name method)) url status) - (println "- action:" action) - (println "- request:\n") - (print-soap-body req-body) - (println) - (when (= http-status/ok status) - (println "- response:\n") - (print-soap-body res-body) - (println)))) - -(defn- print-ooapi-message - "Print boxed OOAPI request and response." - [{{:keys [method url]} :req - {:keys [status body]} :res}] - (print-boxed "OOAPI" - (println (str/upper-case method) url status) - (println) - (when (= http-status/ok status) - (print-json-str body)))) - -(defn- keywordize-keys - "Recursively change map keys to keywords." - [m] - (->> m - (map (fn [[k v]] - [(if (keyword? k) - k - (keyword k)) - (if (map? v) - (keywordize-keys v) - v)])) - (into {}))) - -(defn- print-http-messages + (println (str/upper-case (name method)) url status) + (when body + (printer/print-json body))) + +(defn print-single-http-message [title print-fn msg] + (print-boxed title (print-fn msg))) + +(defn print-http-messages "Print HTTP message as returned by API status." [http-messages] - (when-let [msg (first http-messages)] - ;; need to keywordize-keys because http-message may be translated - ;; from from JSON (in which case they are all keywords) or come - ;; strait from http-utils (which is a mixed bag) - (let [{:keys [req] :as msg} (keywordize-keys msg)] - (if (-> req :headers :SOAPAction) - (print-rio-message msg) - (print-ooapi-message msg))) - (recur (next http-messages)))) + (printer/print-http-messages-with-boxed-printer http-messages print-single-http-message)) @@ -270,7 +200,8 @@ ;; else on error (update res :body dissoc :http-messages) res)] - (print-api-message {:req req, :res res}) + (print-boxed "API" + (print-api-message {:req req, :res res})) (when (seq http-messages) (print-boxed "Job HTTP messages" (print-http-messages http-messages)))