#!/usr/bin/env bb (ns integration-test (:require [babashka.http-client :as http] [cheshire.core :as json] [clojure.edn :as edn] [clojure.string :as str])) (def default-config {:base-url "http://localhost:3000" :max-retries 30 :retry-delay-ms 1000 :watch? false :watch-interval-ms 5000}) (defn get* [base-url] (http/get base-url {:throw false})) (defn head* [base-url] (http/head base-url {:throw false})) ;; The login redirect points at Gitea; we want to see it, not follow it. (def no-redirect-client (http/client {:follow-redirects :never})) (defn get-no-redirect* [url] (http/get url {:throw false :client no-redirect-client})) (defn post-json* [url body] (http/post url {:throw false :headers {"Content-Type" "application/json"} :body body})) (defn post-form* [url params] (http/post url {:throw false :client no-redirect-client :headers {"Content-Type" "application/x-www-form-urlencoded"} :form-params params})) (defn json* [url] (json/parse-string (:body (http/get url)))) (defn wait-for-server [{:keys [base-url max-retries retry-delay-ms]}] (println (str "=== Waiting for server at " base-url " ===")) (loop [i 1] (let [ready? (try (= 200 (:status (get* base-url))) (catch Exception _ false))] (cond ready? (do (println "Server is ready!") true) (= i max-retries) (do (println (format "ERROR: Server did not become ready after %d attempts" max-retries)) false) :else (do (Thread/sleep retry-delay-ms) (recur (inc i))))))) (defn run-suite "Runs the integration suite once against config's :base-url. Returns {:pass n :fail n :errors [...]}" [{:keys [base-url] :as config}] (if-not (wait-for-server config) {:pass 0 :fail 1 :errors ["server did not become ready"]} (let [pass (atom 0) fail (atom 0) errors (atom []) assert-eq (fn [label expected actual] (if (= expected actual) (do (println " ✓" label) (swap! pass inc)) (do (println " ✗" label) (println " expected:" expected) (println " actual: " actual) (swap! fail inc) (swap! errors conj (format "%s: expected '%s' but got '%s'" label expected actual))))) assert-contains (fn [label needle haystack] (if (str/includes? haystack needle) (do (println " ✓" label) (swap! pass inc)) (do (println " ✗" label) (println " expected to contain:" needle) (println " actual: " haystack) (swap! fail inc) (swap! errors conj (format "%s: did not find '%s'" label needle))))) assert-not-empty (fn [label value] (if-not (str/blank? value) (do (println " ✓" label) (swap! pass inc)) (do (println " ✗" label) (println " expected non-empty value") (swap! fail inc) (swap! errors conj (format "%s: value was empty" label)))))] (println) (println "=== Running Integration Tests ===") (println) (println "Test 1: GET / returns 200") (assert-eq "HTTP status" 200 (:status (get* base-url))) (println) (println "Test 2: Content-Type is text/html") (assert-contains "Content-Type" "text/html" (get (:headers (get* base-url)) "content-type" "")) (println) (println "Test 3: Root serves the HTML shell for the UI") (let [body (:body (get* base-url))] (assert-contains "Body has the mount point" "
]" [args] (loop [args args overrides {}] (if (empty? args) overrides (let [[arg & more] args] (cond (= arg "--watch") (recur more (assoc overrides :watch? true)) (= arg "--interval") (recur (rest more) (assoc overrides :watch-interval-ms (parse-long (first more)))) :else (recur more (assoc overrides :base-url arg))))))) (defn -main [& args] (let [config (merge default-config (parse-args args)) {:keys [watch?]} config] (if watch? (do (println "Watch mode: watching for file changes. Press Ctrl+C to stop.") (println "Watching: integration_test.bb") (println) ;; Run once, then wait for inotify events (run-suite config) (println) (println "Waiting for file changes...") (println) ;; Use inotifywait to watch the file for modifications (let [proc (Runtime/exec (into-array String ["inotifywait" "-e" "modify" "-q" "integration_test.bb"]))] (try (.waitFor proc) (finally (.destroy proc))) (recur args))) (let [{:keys [fail]} (run-suite config)] (System/exit (if (pos? fail) 1 0)))))) (comment (run-suite {:base-url "http://localhost:3000"})) ;; bb runs this file as a script, not via -m — nothing calls -main otherwise. (apply -main *command-line-args*)