(ns ird.test-util
"Shared test tooling: fake transports injected through the client's
:http-fn, an instant :sleep-fn, the vendored spec, and the live API key."
(:require-macros [ird.test-util])
(:require [clojure.string :as str]
[cljs.core.async :as async :refer [go]]
[ird.async :as a]))
(def ^:private fs (js/require "node:fs"))
(defn ->json
"BODY as a JSON string: strings pass through, data is serialised."
[body]
(if (string? body) body (js/JSON.stringify (clj->js body))))
(defn capturing
"An :http-fn recording every request into CAPTURED (an atom of a vector)
and answering {} with 200."
[captured]
(fn [req]
(swap! captured conj req)
(go {:status 200 :body "{}"})))
(defn responding
"An :http-fn answering STATUS and BODY (data serialised to JSON, or a raw
string) to every request, recording into CAPTURED when given."
([status body] (responding (atom []) status body))
([captured status body]
(fn [req]
(swap! captured conj req)
(go {:status status :body (->json body)}))))
(defn scripted
"An :http-fn answering successive requests with the next [status body] of
RESPONSES, recording into CAPTURED; once exhausted it delivers an
ExceptionInfo (which the transport reports as a transport error)."
[captured responses]
(let [queue (atom (vec responses))]
(fn [req]
(swap! captured conj req)
(let [[status body] (first @queue)]
(swap! queue #(if (seq %) (subvec % 1) %))
(go (if status
{:status status :body (->json body)}
(ex-info "Scripted responses exhausted" {:url (:url req)})))))))
(defn failing
"An :http-fn whose channel delivers ERR (an Error) — a connection failure."
[err]
(fn [_] (go err)))
(defn no-sleep
"A :sleep-fn that never waits."
[_ms]
(doto (async/chan) async/close!))
(defn json-body
"The request's JSON body, parsed with keyword keys."
[req]
(js->clj (js/JSON.parse (:body req)) :keywordize-keys true))
(defn path-of
"The request's URL without the API base."
[req]
(subs (:url req) (count "https://api.ird.ro")))
(defn op-of
"\"METHOD /path\" of a request, for comparing request sequences."
[req]
(str (str/upper-case (name (:method req))) " " (path-of req)))
(defn spec
"The vendored OpenAPI spec (string keys)."
[]
(js->clj (js/JSON.parse (.readFileSync fs "spec/openapi.json" "utf8"))))
(defn spec-operations
"Every \"METHOD /path\" the vendored spec declares."
[]
(set (for [[path item] (get (spec) "paths")
[method _] item
:when (contains? #{"get" "post" "put" "patch" "delete"} method)]
(str (str/upper-case method) " " path))))
(defn api-key
"IRD_API_KEY from the environment, else from a local .env file
(KEY=value lines); nil when neither has it."
[]
(or (not-empty (str/trim (or (.. js/process -env -IRD_API_KEY) "")))
(when (.existsSync fs ".env")
(some (fn [line]
(let [line (str/trim line)]
(when (str/starts-with? line "IRD_API_KEY=")
(-> (subs line (count "IRD_API_KEY="))
str/trim
(str/replace #"^[\"']|[\"']$" "")
not-empty))))
(str/split-lines (.readFileSync fs ".env" "utf8"))))))
(def error? a/error?)