Files
tbd/integration-test/integration_test.bb
ajet 93c4b340e4
CI / Unit tests (push) Failing after 1m0s
CI / Integration tests + coverage (push) Has been skipped
making franz happy
2026-09-19 05:50:34 -09:00

180 lines
7.3 KiB
Clojure
Executable File

#!/usr/bin/env bb
(ns integration-test
(:require [babashka.http-client :as http]
[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}))
(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/plain")
(assert-contains "Content-Type" "text/plain" (get (:headers (get* base-url)) "content-type" ""))
(println)
(println "Test 3: Root response points at the API docs")
(let [body (:body (get* base-url))]
(assert-contains "Body mentions /api-docs" "/api-docs" body)
(assert-not-empty "Body is non-empty" body)
(println)
(println "Test 4: Response is plain text, not HTML")
(if (and (not (str/includes? body "<!DOCTYPE"))
(not (str/includes? body "<html")))
(do (println " ✓ Response is not HTML") (swap! pass inc))
(do (println " ✗ Response appears to be HTML")
(swap! fail inc)
(swap! errors conj "Response was HTML, expected plain text"))))
(println)
(println "Test 5: HEAD / returns 200")
(assert-eq "HEAD status" 200 (:status (head* base-url)))
(println)
(println "Test 6: GET /api/ping returns 200 with body 'pong'")
(let [resp (get* (str base-url "/api/ping"))]
(assert-eq "HTTP status" 200 (:status resp))
(assert-eq "Body" "pong" (:body resp)))
(println)
(println "Test 7: GET /swagger.json returns a valid-looking spec")
(let [resp (get* (str base-url "/swagger.json"))
body (:body resp)]
(assert-eq "HTTP status" 200 (:status resp))
(assert-contains "Content-Type" "application/json" (get (:headers resp) "content-type" ""))
(assert-contains "Spec references /api/ping" "/api/ping" body))
(println)
(println "Test 8: GET /api-docs serves the Swagger UI")
(let [resp (get* (str base-url "/api-docs"))]
(assert-eq "HTTP status" 200 (:status resp))
(assert-contains "Content-Type" "text/html" (get (:headers resp) "content-type" "")))
(println)
(println "Test 9: GET /api/add?x=2&y=3 returns {:result 5} as EDN")
(let [resp (get* (str base-url "/api/add?x=2&y=3"))]
(assert-eq "HTTP status" 200 (:status resp))
(assert-contains "Content-Type" "application/edn" (get (:headers resp) "content-type" ""))
(assert-eq "Body" {:result 5} (edn/read-string (:body resp))))
(println)
(println "Test 10: GET /api/add with a missing or non-numeric param returns 400")
(assert-eq "Missing y" 400 (:status (get* (str base-url "/api/add?x=2"))))
(assert-eq "Non-numeric x" 400 (:status (get* (str base-url "/api/add?x=abc&y=3"))))
(println)
(println "================================")
(println (format " Results: %d passed, %d failed" @pass @fail))
(println "================================")
(if (pos? @fail)
(do (println) (println "Failures:") (doseq [e @errors] (println (str " - " e))))
(do (println) (println "All integration tests passed! ✓")))
{:pass @pass :fail @fail :errors @errors})))
(defn parse-args
"Parses CLI args into a config override map.
Supported: [base-url] [--watch] [--interval <ms>]"
[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*)