Skip to content

Commit 1b7cfed

Browse files
authored
Merge pull request #5 from noblepayne/bugfix/improve-validation
test: add Fountain test boost
2 parents 4fbddca + 7a7aeb5 commit 1b7cfed

File tree

2 files changed

+105
-28
lines changed

2 files changed

+105
-28
lines changed

src/boostbox/boostbox.clj

Lines changed: 27 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -236,33 +236,34 @@
236236
;; provided by us
237237
#_[:id {:optional true} :string]
238238
;; provided by boost client
239-
[:action [:enum :boost :stream]]
239+
[:action {:decode/json str/lower-case
240+
:decode/string str/lower-case} [:enum "boost" "stream"]]
240241
[:split {:json-schema/default 1.0} [:double {:min 0.0}]]
241242
[:value_msat {:json-schema/default 2222000} [:int {:min 1}]]
242243
[:value_msat_total {:json-schema/default 2222000} [:int {:min 1}]]
243244
[:timestamp {:json-schema/default (java.time.Instant/now)}
244245
[:and :string
245246
[:fn {:error/message "must be ISO-8601"} valid-iso8601?]]]
246-
;; optional keys
247-
[:group {:optional true} :string]
248-
[:message {:optional true :json-schema/default "row of ducks"} :string]
249-
[:app_name {:optional true} :string]
250-
[:app_version {:optional true} :string]
251-
[:sender_id {:optional true} :string]
252-
[:sender_name {:optional true} :string]
253-
[:recipient_name {:optional true} :string]
254-
[:recipient_address {:optional true} :string]
255-
[:value_usd {:optional true} [:double {:min 0.0}]]
256-
[:position {:optional true} :int]
257-
[:feed_guid {:optional true} :string]
258-
[:feed_title {:optional true} :string]
259-
[:item_guid {:optional true} :string]
260-
[:item_title {:optional true} :string]
261-
[:publisher_guid {:optional true} :string]
262-
[:publisher_title {:optional true} :string]
263-
[:remote_feed_guid {:optional true} :string]
264-
[:remote_item_guid {:optional true} :string]
265-
[:remote_publisher_guid {:optional true} :string]])
247+
;; optional keys
248+
[:group {:optional true} [:maybe :string]]
249+
[:message {:optional true :json-schema/default "row of ducks"} [:maybe :string]]
250+
[:app_name {:optional true} [:maybe :string]]
251+
[:app_version {:optional true} [:maybe :string]]
252+
[:sender_id {:optional true} [:maybe :string]]
253+
[:sender_name {:optional true} [:maybe :string]]
254+
[:recipient_name {:optional true} [:maybe :string]]
255+
[:recipient_address {:optional true} [:maybe :string]]
256+
[:value_usd {:optional true} [:maybe [:double {:min 0.0}]]]
257+
[:position {:optional true} [:maybe :int]]
258+
[:feed_guid {:optional true} [:maybe :string]]
259+
[:feed_title {:optional true} [:maybe :string]]
260+
[:item_guid {:optional true} [:maybe :string]]
261+
[:item_title {:optional true} [:maybe :string]]
262+
[:publisher_guid {:optional true} [:maybe :string]]
263+
[:publisher_title {:optional true} [:maybe :string]]
264+
[:remote_feed_guid {:optional true} [:maybe :string]]
265+
[:remote_item_guid {:optional true} [:maybe :string]]
266+
[:remote_publisher_guid {:optional true} [:maybe :string]]])
266267

267268
;; ~~~~~~~~~~~~~~~~~~~ GET View ~~~~~~~~~~~~~~~~~~~
268269
(defn encode-header [data]
@@ -292,7 +293,7 @@
292293
(let [boost-id (get data "id")
293294
json-pretty (json/write-value-as-string data (json/object-mapper {:pretty true}))
294295
sender-name (get data "sender_name")
295-
value-msats (get data "value_msat")
296+
value-msats (get data "value_msat_total")
296297
sats (format-sats value-msats)
297298
feed-title (get data "feed_title")
298299
item-title (get data "item_title")
@@ -390,7 +391,7 @@
390391
(str "rss::payment::" action " " url)))
391392

392393
(defn add-boost [cfg storage]
393-
(fn [{:keys [:body-params] :as request}]
394+
(fn [{{body-params :body} :parameters :as request}]
394395
(let [id (gen-ulid)
395396
url (str (:base-url cfg) "/boost/" id)
396397
boost (assoc body-params :id id)
@@ -489,6 +490,7 @@
489490
:coercion (reitit.coercion.malli/create
490491
{:error-keys #{:in :humanized}
491492
:compile mu/open-schema
493+
:strip-extra-keys false
492494
:default-values true})
493495
:middleware [swagger/swagger-feature
494496
parameters/parameters-middleware
@@ -531,14 +533,11 @@
531533
(assoc-in response [:headers "x-correlation-id"] correlation-id))))
532534

533535
(defn mulog-wrapper [handler]
534-
(fn [{:keys [:request-method :uri :query-params :path-params :body-params :correlation-id] :as request}]
536+
(fn [{:keys [:request-method :uri :correlation-id] :as request}]
535537
(u/trace ::http-request
536538
{:pairs [:correlation-id correlation-id
537539
:method request-method
538-
:uri uri
539-
:query-params query-params
540-
:path-params path-params
541-
:body-params body-params]
540+
:uri uri]
542541
:capture (fn [{:keys [:status ::exception] :as response}]
543542
(let [success (< status 400)
544543
base {:status status

test/boostbox/boostbox_test.clj

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,84 @@
242242
;; Check we received our sent payload plus id.
243243
(is (= expected decoded-json))))))))))))
244244

245+
246+
(deftest test-oscar-fountain-boost
247+
(run-with-storage
248+
["FS" "S3"]
249+
(fn [data]
250+
(testing "Oscar's real Fountain boost - extra fields, nulls, and case handling"
251+
(let [base-url (-> data :config :base-url)
252+
api-key (-> data :config :allowed-keys first)
253+
;; Oscar's actual boost with extras and nulls
254+
oscar-boost {:action "BOOST" ; uppercase to test normalization
255+
:split 0.05
256+
:message "Test Boost 2 👀👀👀👀👀👀"
257+
:link "https://fountain.fm/episode/JCIzq3VyFKQVkEzVNA8v?payment=5XIAt66P29Iv6rjSTZUB"
258+
:app_name "Fountain"
259+
:sender_id "hIWsCYxdBJzlDvu5zpT3"
260+
:sender_name "[email protected]"
261+
:sender_npub "npub1unmftuzmkpdjxyj4en8r63cm34uuvjn9hnxqz3nz6fls7l5jzzfqtvd0j2"
262+
:recipient_address "[email protected]"
263+
:value_msat 50000
264+
:value_usd 0.049998
265+
:value_msat_total 1000000
266+
:timestamp "2025-11-07T14:36:23.861Z"
267+
:position 5192
268+
:feed_guid "917393e3-1b1e-5cef-ace4-edaa54e1f810"
269+
:feed_title "Podcasting 2.0"
270+
:item_guid "PC20-240"
271+
:item_title "Episode 240: Open Source = People!"
272+
:publisher_guid nil
273+
:publisher_title nil
274+
:remote_feed_guid nil
275+
:remote_item_guid nil
276+
:remote_publisher_guid nil}
277+
278+
post-resp (http/post (str base-url "/boost")
279+
{:headers {"x-api-key" api-key
280+
"Content-Type" "application/json"}
281+
:body (json/write-value-as-string oscar-boost)
282+
:throw false})]
283+
284+
(is (= 201 (:status post-resp)) "Should accept Oscar's boost")
285+
286+
(let [post-body (json/read-value (:body post-resp))
287+
boost-id (get post-body "id")]
288+
(when boost-id
289+
(let [
290+
boost-url (get post-body "url")
291+
get-resp (http/get boost-url {:throw false})
292+
header (get-in get-resp [:headers "x-rss-payment"])
293+
decoded (-> header
294+
(java.net.URLDecoder/decode "UTF-8")
295+
(json/read-value))]
296+
297+
(is (= 200 (:status get-resp)) "GET should return 200")
298+
299+
;; Verify normalization
300+
(is (= "boost" (get decoded "action"))
301+
"Action should be lowercased from BOOST")
302+
303+
;; Verify extra fields pass through (not in schema)
304+
(is (= "https://fountain.fm/episode/JCIzq3VyFKQVkEzVNA8v?payment=5XIAt66P29Iv6rjSTZUB"
305+
(get decoded "link"))
306+
"Extra field 'link' should be preserved")
307+
(is (= "npub1unmftuzmkpdjxyj4en8r63cm34uuvjn9hnxqz3nz6fls7l5jzzfqtvd0j2"
308+
(get decoded "sender_npub"))
309+
"Extra field 'sender_npub' should be preserved")
310+
311+
;; Verify null handling
312+
(is (nil? (get decoded "publisher_guid"))
313+
"Null values should be preserved")
314+
(is (nil? (get decoded "remote_feed_guid"))
315+
"Multiple null fields should be preserved")
316+
317+
;; Verify HTML renders without errors
318+
(is (str/includes? (:body get-resp) "Boost Details")
319+
"HTML view should render successfully")
320+
(is (str/includes? (:body get-resp) "Test Boost 2")
321+
"HTML should show the message")))))))))
322+
245323
;; --- Unhappy Path Smoke Tests ---
246324

247325
(deftest smoke-test-413-payload-too-large

0 commit comments

Comments
 (0)