Side A eliminates substantial duplicated HTTP/OAuth/test-harness code across four test files by consolidating it into shared common.bb/oauth.bb helpers, reducing maintenance burden and duplication risk with no behavior loss. Side B is a small, localized UI tweak (moving a delete button) with modest CSS/markup changes that fixes a cosmetic layout issue but has narrower lasting impact on the codebase.
constitution · epochs · watch · epoch 3
c_48fcbcde8f88 (tommy-mor) vs c_fc17a4aa31e6 (tommy-mor)
download prompt · raw event · cmp_1fbd8559143dc1
council reasoning
Commit A consolidates duplicated test harness, HTTP/OAuth mocks, registration helpers, cargo build, and server env into shared modules (common.bb/oauth.bb), shrinking auth/grants/integration and improving maintainability. Commit B only relocates the delete control into post_header_meta with flex CSS tweaks—presentational churn with little lasting design impact.
Side A consolidates duplicated test infrastructure into shared utilities by introducing reusable helpers in `test.common` and `test.oauth` (shared assertions, server environment creation, cargo build wrapper, OAuth registration flow, configurable mock Google server, and common HTTP behavior with timeouts), then updates multiple test suites to use them. This reduces duplication and centralizes maintenance across the integration tests, whereas Side B is primarily a UI layout refactor that moves the delete button into the metadata component and adjusts CSS flexbox styling without changing core functionality.
sides
A — c_48fcbcde8f88 (tommy-mor)
message
[b32c92f2] consolidated shared functions in tests (composer 2 fast)
diff preview
diff --git a/test/auth.bb b/test/auth.bb
index a67cc1de763434176d2f68e419dd37a8cd328395..78cffc875ffe83f512bca253f4a7489227311da4 100644
--- a/test/auth.bb
+++ b/test/auth.bb
@@ -3,113 +3,14 @@
(:require [babashka.process :as p]
[babashka.fs :as fs]
[cheshire.core :as json]
- [org.httpkit.server :as http]
- [test.common :as common]))
+ [clojure.string :as str]
+ [test.common :as common]
+ [test.oauth :as oauth]))
-(def ^:private ansi-green "\033[32m")
-(def ^:private ansi-red "\033[31m")
-(def ^:private ansi-reset "\033[0m")
(def ^:private counts (atom {:pass 0 :fail 0}))
-(defn- pass [msg]
- (swap! counts update :pass inc)
- (println (str ansi-green " ✓ " ansi-reset msg)))
-
-(defn- fail [msg]
- (swap! counts update :fail inc)
- (println (str ansi-red " ✗ " ansi-reset msg)))
-
(defn- assert! [pred msg]
- (if pred
- (pass msg)
- (do (fail msg)
- (throw (ex-info (str "FAIL: " msg) {})))))
-
-(defn- http-client []
- (-> (java.net.http.HttpClient/newBuilder)
- (.followRedirects java.net.http.HttpClient$Redirect/ALWAYS)
- (.build)))
-
-(defn- http-get [url & {:keys [headers]}]
- (let [b (java.net.http.HttpRequest/newBuilder (java.net.URI/create url))]
- (doseq [[k v] (or headers {})]
- (.header b k v))
- (let [req (-> b (.GET) (.build))
- resp (.send (http-client) req (java.net.http.HttpResponse$BodyHandlers/ofString))]
- {:status (.statusCode resp) :body (.body resp) :headers (.map (.headers resp))})))
-
-(defn- http-post-json [url data & {:keys [headers]}]
- (let [body (json/generate-string data)
- b (java.net.http.HttpRequest/newBuilder (java.net.URI/create url))]
- (.header b "Content-Type" "application/json")
- (doseq [[k v] (or headers {})]
- (.header b k v))
- (let [req (-> b
- (.POST (java.net.http.HttpRequest$BodyPublishers/ofString body))
- (.build))
- resp (.send (http-client) req (java.net.http.HttpResponse$BodyHandlers/ofString))]
- {:status (.statusCode resp) :body (.body resp) :headers (.map (.headers resp))})))
-
-(defn- http-post-form [url form]
- (let [pairs (->> form
- (map (fn [[k v]]
- (str (java.net.URLEncoder/encode (name k) "UTF-8")
- "="
- (java.net.URLEncoder/encode (str v) "UTF-8"))))
- (clojure.string/join "&"))
- b (java.net.http.HttpRequest/newBuilder (java.net.URI/create url))]
- (.header b "Content-Type" "application/x-www-form-urlencoded")
- (let [req (-> b
- (.POST (java.net.http.HttpRequest$BodyPublishers/ofString pairs))
- (.build))
- resp (.send (http-client) req (java.net.http.HttpResponse$BodyHandlers/ofString))]
- {:status (.statusCode resp) :body (.body resp) :headers (.map (.headers resp))})))
-
-(defn- parse-query [s]
- (into {}
- (for [part (clojure.string/split (or s "") #"&")
- :when (not (clojure.string/blank? part))]
- (let [[k v] (clojure.string/split part #"=" 2)]
- [(keyword (java.net.URLDecoder/decode k "UTF-8"))
- (some-> v (java.net.URLDecoder/decode "UTF-8"))]))))
-
-(defn- b64url [s]
- (-> (java.util.Base64/getUrlEncoder)
- (.withoutPadding)
- (.encodeToString (.getBytes s "UTF-8"))))
-
-(defn- make-id-token [sub]
- (str (b64url "{\"alg\":\"RS256\",\"typ\":\"JWT\"}")
- "."
- (b64url (json/generate-string {:sub sub}))
- ".fakesig"))
-
-(defn- start-mock-google [port]
- (let [google-users ["google-user-1" "google-user-2"]
- !call-count (atom 0)
- handler
- (fn [req]
- (cond
- (and (= :get (:request-method req))
- (= "/o/oauth2/v2/auth" (:uri req)))
- (let [q (parse-query (:query-string req))
- redirect-uri (:redirect_uri q)
- state (:state q)
- loc (str redirect-uri "?code=mockcode&state=" state)]
- {:status 302 :headers {"Location" loc} :body ""})
-
- (and (= :post (:request-method req))
- (= "/token" (:uri req)))
- (let [n (swap! !call-count inc)
- sub (nth google-users (mod (dec n) (count google-users)))]
- {:status 200
- :headers {"Content-Type" "application/json"}
- :body (json/generate-string {:id_token (make-id-token sub)})})
-
- :else
- {:status 404 :body "not found"}))
- stop-fn (http/run-server handler {:port port})]
- {:stop-fn stop-fn :port port}))
+ (common/test-assert! counts pred msg))
(defn auth-test [& _args]
(println "\n━━━ auth v3 integration check ━━━\n")
@@ -117,8 +18,7 @@
(println "building server + CLI binaries…")
(common/letlocals
- (bind build @(p/process [(common/cargo-bin) "build" "--release" "-p" "slugsocial-server" "-p" "slugsocial"]
- {:inherit true :env common/base-env}))
+ (bind build (common/run-cargo-build-release! ["slugsocial-server" "slugsocial"]))
(assert! (zero? (:exit build)) "cargo build succeeds")
(bind server-bin "target/release/slugsocial-server")
(bind cli-bin "target/release/slugsocial")
@@ -134,19 +34,10 @@
(bind !server (atom nil))
(bind !google (atom nil))
- (bind server-env (merge common/base-env
- {"SLUG_DATA_DIR" tmp-dir
- "SLUG_KEYS" "test:test"
- "PORT" (str slug-port)
- "RUST_LOG" "warn"
- "SLUG_PUBLIC_URL" base-url
- "SLUG_GOOGLE_AUTH_URL" (str google-url "/o/oauth2/v2/auth")
- "SLUG_GOOGLE_TOKEN_URL" (str google-url "/token")
- "SLUG_GOOGLE_CLIENT_ID" "mock"
- "SLUG_GOOGLE_CLIENT_SECRET" "mock"}))
+ (bind server-env (common/slug-server-env tmp-dir base-url google-url slug-port))
(try
(println (str "\nstarting mock google on :" google-port))
- (reset! !google (start-mock-google google-port))
+ (reset! !google (oauth/start-mock-google google-port :google-users ["google-user-1" "google-user-2"]))
(assert! (some? (:stop-fn @!google)) "mock google started")
(println (str "starting server on :" slug-port))
@@ -154,34 +45,33 @@
(assert! (common/wait-for-server base-url 10000) "server responds to /healthz")
(println "\nstarting pending session…")
- (let [start-resp (http-post-json (str base-url "/api/v0/pending-session")
- {:agent "00000000-0000-0000-0000-000000000000:bb:local/dev"})
+ (let [start-resp (oauth/http-post-json (str base-url "/api/v0/pending-session")
+ {:agent "00000000-0000-0000-0000-000000000000:bb:local/dev"})
_ (assert! (= 200 (:status start-resp)) "pending-session start returns 200")
start-json (json/parse-string (:body start-resp) true)]
- (assert! (clojure.string/starts-with? (:session start-json) "p_") "session id has p_ prefix")
- (assert! (clojure.string/includes? (:login_url start-json) "/auth/login") "login_url provided")
+ (assert! (str/starts-with? (:session start-json) "p_") "session id has p_ prefix")
+ (assert! (str/includes? (:login_url start-json) "/auth/login") "login_url provided")
(println "\nsimulating browser oauth redirects…")
- ;; This will follow redirects: /auth/login -> mock google -> /auth/callback -> /auth/choose-username
- (let [login-get (http-get (:login_url start-json))]
+ (let [login-get (oauth/http-get (:login_url start-json))]
(assert! (= 200 (:status login-get)) "choose-username page reachable after oauth callback"))
(println "\nchoosing username…")
- (let [choose (http-post-form (str base-url "/auth/choose-username")
- {:session (:session start-json) :username "bbuser"})]
+ (let [choose (oauth/http-post-form (str base-url "/auth/choose-username")
+ {:session (:session start-json) :username "bbuser"})]
(assert! (= 200 (:status choose)) "choose-username POST returns 200"))
(println "\npolling pending session…")
- (let [poll (http-get (str base-url "/api/v0/pending-session/" (:session start-json)))]
+ (let [poll (oauth/http-get (str base-url "/api/v0/pending-session/" (:session start-json)))]
(assert! (= 200 (:status poll)) "pending-session poll returns 200")
(let [poll-json (json/parse-string (:body poll) true)]
(assert! (:complete poll-json) "pending session complete=true")
(assert! (= "bbuser" (:user poll-json)) "poll returns stored username bbuser")
- (assert! (clojure.string/starts-with? (:token poll-json) "slug_") "poll returns bearer token")
+ (assert! (str/starts-with? (:token poll-json) "slug_") "poll returns bearer token")
(println "\nwhoami…")
- (let [who (http-get (str base-url "/api/v0/whoami")
- :headers {"Authorization" (str "Bearer " (:token poll-json))})]
+ (let [who (oauth/http-get (str base-url "/api/v0/whoami")
+ :headers {"Authorization" (str "Bearer " (:token poll-json))})]
(assert! (= 200 (:status who)) "whoami returns 200")
(let [who-json (json/parse-string (:body who) true)]
(assert! (= "bbuser" (:user who-json)) "whoami user is bbuser (stored form)"))))))
@@ -195,11 +85,11 @@
(str "identity start exits 0 (stderr: " (:err start-proc) ")"))
(let [start-cli (json/parse-string (:out start-proc) true)]
(assert! (= "present_oauth_url_to_user" (:phase start-cli)) "identity start --json phase")
- (assert! (clojure.string/starts-with? (:session start-cli) "p_") "CLI start session id")
- (let [login-get (http-get (:login_url start-cli))]
+ (assert! (str/starts-with? (:session start-cli) "p_") "CLI start session id")
+ (let [login-get (oauth/http-get (:login_url start-cli))]
(assert! (= 200 (:status login-get)) "CLI login_url redirect chain succeeds"))
- (let [choose (http-post-form (str base-url "/auth/choose-username")
- {:session (:session start-cli) :username "cliuser"})]
+ (let [choose (oauth/http-post-form (str base-url "/auth/choose-username")
+ {:session (:session start-cli) :username "cliuser"})]
(assert! (= 200 (:status choose)) "choose-username for cliuser"))
(let [poll-proc @(p/process [cli-bin "identity" "poll" (:session start-cli)
"--poll-interval-ms" "100" "--max-wait-secs" "30" "--json"]
@@ -209,10 +99,10 @@
(let [poll-cli (json/parse-string (:out poll-proc) true)]
(assert! (= "complete" (:phase poll-cli)) "identity poll --json phase")
(assert! (= "cliuser" (:user poll-cli)) "CLI poll user (stored form)")
- (assert! (clojure.string/starts-with? (:token poll-cli) "slug_") "CLI poll token")
+ (assert! (str/starts-with? (:token poll-cli) "slug_") "CLI poll token")
(let [token-path (str cli-home "/.config/slugsocial/token")]
(assert! (fs/exists? token-path) "token written under isolated HOME")
- (assert! (= (clojure.string/trim (slurp token-path)) (:token poll-cli))
+ (assert! (= (str/trim (slurp token-path)) (:token poll-cli))
"token file matches poll JSON"))
… preview truncated; 24,704 characters omittedB — c_fc17a4aa31e6 (tommy-mor)
message
[af6c4464] moved delete button
diff preview
diff --git a/server/src/html/forum/ingest.rs b/server/src/html/forum/ingest.rs
index 168106e495cf1a81f62f6a49e35c64beea287924..308358abc6cd6811079d9ec41b114093c0294d02 100644
--- a/server/src/html/forum/ingest.rs
+++ b/server/src/html/forum/ingest.rs
@@ -45,6 +45,7 @@ fn post_header_meta(
principal: &str,
ts: i64,
now: i64,
+ delete_post_id: Option<&str>,
) -> Markup {
let post_href = nav.post_url(tag, post_idx);
let profile = profile_href(principal);
@@ -52,11 +53,19 @@ fn post_header_meta(
let ago = timeago::timeago(now, ts);
html! {
div class="ingest-meta muted" title=(hover) {
- a href=(post_href) class="post-num" { "#" (post_idx) }
- " "
- a href=(profile) class="post-author" { "@" (principal) }
- " · "
- (ago)
+ span class="ingest-meta-primary" {
+ a href=(post_href) class="post-num" { "#" (post_idx) }
+ " "
+ a href=(profile) class="post-author" { "@" (principal) }
+ " · "
+ (ago)
+ }
+ @if let Some(pid) = delete_post_id {
+ form class="post-delete-form" method="POST" action="/ui" {
+ input type="hidden" name=(UI_RPC_FIELD) value=(template_json_compact(&HtmlUiAction::RedactPost { post_id: pid.to_string() }).unwrap());
+ button type="submit" class="post-delete-btn" { "delete" }
+ }
+ }
}
}
}
@@ -70,18 +79,12 @@ pub(super) fn post_header_row(
now: i64,
show_delete: bool,
) -> Markup {
- let meta = post_header_meta(nav, tag, post_idx, &ing.principal, ing.ts, now);
- html! {
- div class="ingest-header-row" {
- (meta)
- @if show_delete {
- form class="post-delete-form" method="POST" action="/ui" {
- input type="hidden" name=(UI_RPC_FIELD) value=(template_json_compact(&HtmlUiAction::RedactPost { post_id: ing.id.clone() }).unwrap());
- button type="submit" class="post-delete-btn" { "delete" }
- }
- }
- }
- }
+ let delete_post_id = if show_delete {
+ Some(ing.id.as_str())
+ } else {
+ None
+ };
+ post_header_meta(nav, tag, post_idx, &ing.principal, ing.ts, now, delete_post_id)
}
pub(super) fn redacted_header_row(
@@ -92,7 +95,7 @@ pub(super) fn redacted_header_row(
now: i64,
expanded: bool,
) -> Markup {
- let meta = post_header_meta(nav, tag, post_idx, &ing.principal, ing.ts, now);
+ let meta = post_header_meta(nav, tag, post_idx, &ing.principal, ing.ts, now, None);
let rpc_expand = template_json_compact(&json!({
"action": "expand_redacted_post",
"room": nav.room_wire,
diff --git a/server/static/theme_default.css b/server/static/theme_default.css
index 560881c1eec62f12d1e66875287be3c2f39198fc..83b49d079e6bf3e47c27fbd443a0ae75b1a03051 100644
--- a/server/static/theme_default.css
+++ b/server/static/theme_default.css
@@ -552,12 +552,21 @@ pre a.pre-link {
}
div.ingest-meta {
+ align-items: center;
background: var(--g3);
border-bottom: 2px solid var(--lo);
color: var(--meta);
+ display: flex;
+ flex-wrap: wrap;
font-size: 12px;
+ gap: 6px 10px;
+ justify-content: space-between;
padding: 3px 10px;
}
+span.ingest-meta-primary {
+ flex: 1 1 auto;
+ min-width: 0;
+}
a.post-num { color: var(--meta); font-size: 12px; }
a.post-num:hover { color: var(--signal); }
a.post-author { color: var(--meta); font-size: 12px; text-decoration: none; }
@@ -579,7 +588,12 @@ div.ingest-header-row div.ingest-meta {
flex: 1 1 auto;
padding: 0;
}
-form.post-delete-form { display: inline; margin: 0; }
+form.post-delete-form {
+ display: block;
+ flex-shrink: 0;
+ margin: 0;
+ margin-left: auto;
+}
button.post-delete-btn {
background: var(--g4);
border: var(--bv) solid;
diff --git a/server/static/theme_retro_craft.css b/server/static/theme_retro_craft.css
index eb022a8c84c1d0cff36410541872d68a11195e31..7181c7f8547940a402ae0211220fd6ee93ee1364 100644
--- a/server/static/theme_retro_craft.css
+++ b/server/static/theme_retro_craft.css
@@ -132,8 +132,22 @@ p.post-truncation-action {
margin: 0;
}
div.ingest-meta {
+ align-items: center;
border-bottom: 1px solid var(--line);
- padding: 0.35rem 0.65rem;
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: space-between;
+ padding-left: 0.35rem;
+}
+span.ingest-meta-primary {
+ flex: 1 1 auto;
+ min-width: 0;
+}
+form.post-delete-form {
+ display: block;
+ flex-shrink: 0;
+ margin: 0;
+ margin-left: auto;
}
a.post-num {
color: var(--ink-dim);
Hardlinks — judgments / attempts / prompt
judgments
attempts
Prompt text is loaded only by the download route.