(ns ird.p2p.api-purity-test
"The public-API hard rule, enforced. Every public namespace — every
src/ird/p2p/**/*.cljs EXCEPT src/ird/p2p/impl/** — must contain zero JS
interop: no `^js` meta, no `js/...`, no `.-` property access, no `#js`
literal, no `js-obj`, no `goog/`, no `aget`/`aset`. All values that cross
a public boundary are pure ClojureScript data plus core.async channels;
bytes travel as base64 strings, with the impl namespaces doing the
conversion. This test scans the source files and fails listing every
offending file and pattern."
(:require [clojure.string :as str]
[cljs.test :refer [deftest is]]))
(def ^:private fs (js/require "node:fs"))
(def ^:private path (js/require "node:path"))
(defn- cljs-files [dir]
(mapcat (fn [entry]
(let [full (.join path dir (.-name entry))]
(cond
(.isDirectory entry) (cljs-files full)
(str/ends-with? (.-name entry) ".cljs") [full]
:else [])))
(.readdirSync fs dir #js {:withFileTypes true})))
(def ^:private forbidden
["^js" "js/" ".-" "#js" "js-obj" "goog/" "(aget" "(aset"])
(defn- public-ns-files []
(->> (cljs-files "src/ird/p2p")
(remove #(str/includes? % "/impl/"))
(cons "src/ird/p2p.cljs")
(filter #(.existsSync fs %))
distinct
sort))
(deftest public-namespaces-have-zero-js-interop
(let [files (public-ns-files)]
(is (<= 11 (count files)) "the public surface is present")
(doseq [f files]
(let [src (.readFileSync fs f "utf8")
hits (filter #(str/includes? src %) forbidden)]
(is (empty? hits) (str f " contains forbidden JS interop: " (pr-str (vec hits))
" — interop belongs under src/ird/p2p/impl/"))))))
(deftest impl-is-the-only-interop-boundary
;; a sanity check that the scan actually looks at real files, not an empty set
(let [impl (filter #(str/includes? % "/impl/") (cljs-files "src/ird/p2p"))]
(is (seq impl) "there are impl namespaces")
(is (some (fn [f] (str/includes? (.readFileSync fs f "utf8") "js/")) impl)
"impl namespaces do use interop, so the scan distinguishes them")))