host: blog post CRUD (list/create/update/delete) + fail-loud test runner, 175/175
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 37s
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 37s
CRUD on the durable content store, per-request IO:
GET /posts list (public) -> [{slug,title}]
GET /<slug>/ read (public) -> HTML / 404
POST /posts create (auth+ACL edit/blog) -> 201/400/409
PUT /posts/<slug> update title+body -> 200/400/404
DELETE /posts/<slug> delete (truncate) -> 200/404
Writes behind the auth+ACL pipeline; create=insert ops, update=op-updates,
delete=stream truncate. 16 new CRUD tests (full lifecycle + 401/403/409/404).
GOTCHA fixed: is a reserved CEK special form — a (let ((guard ...)))
helper was shadowed by it ((guard h) ran the guard special form -> 'first:
expected list'). Renamed to host/blog--protect; namespace-prefix all helpers.
HARDENING: conformance.sh now FAILS LOUD on load/eval errors. A test file that
errors mid-load silently truncates its suite and reports a false green (this hid
the CRUD failure as 'blog 13 passed, 0 failed'). The runner greps for error
markers and aborts. Documented the SX gotcha set + prevention ladder in the plan.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -82,6 +82,85 @@
|
||||
(contains? (dream-resp-body (host-bl-app (host-bl-req "/feed"))) "\"ok\":true")
|
||||
true)
|
||||
|
||||
;; ── CRUD: list / create / update / delete (writes auth+ACL guarded) ─
|
||||
(acl/load! (list (acl-grant "editor" "edit" "blog")))
|
||||
(define host-bl-resolve
|
||||
(fn (tok) (cond ((= tok "good") "editor") ((= tok "weak") "reader") (true nil))))
|
||||
(define host-bl-wapp
|
||||
(host/make-app (list (host/blog-write-routes host-bl-resolve) host/blog-routes)))
|
||||
(define host-bl-send
|
||||
(fn (method target auth body)
|
||||
(dream-request method target (if auth {:authorization auth} {}) body)))
|
||||
|
||||
;; start from a clean store
|
||||
(host/blog-use-store! (persist/open))
|
||||
|
||||
;; list empty
|
||||
(host-bl-test "list empty -> data:[]"
|
||||
(contains? (dream-resp-body (host-bl-wapp (host-bl-send "GET" "/posts" nil ""))) "\"data\":[]")
|
||||
true)
|
||||
|
||||
;; create requires auth
|
||||
(host-bl-test "create no auth -> 401"
|
||||
(dream-status (host-bl-wapp (host-bl-send "POST" "/posts" nil "{}")))
|
||||
401)
|
||||
(host-bl-test "create authed-unpermitted -> 403"
|
||||
(dream-status (host-bl-wapp (host-bl-send "POST" "/posts" "Bearer weak"
|
||||
"{\"slug\":\"hello\",\"title\":\"Hi\",\"body\":\"B\"}")))
|
||||
403)
|
||||
;; create permitted -> 201
|
||||
(host-bl-test "create -> 201"
|
||||
(dream-status (host-bl-wapp (host-bl-send "POST" "/posts" "Bearer good"
|
||||
"{\"slug\":\"hello\",\"title\":\"Hello World\",\"body\":\"First post.\"}")))
|
||||
201)
|
||||
;; created post renders at GET /<slug>/
|
||||
(host-bl-test "created post reads back as HTML"
|
||||
(contains? (dream-resp-body (host-bl-wapp (host-bl-send "GET" "/hello/" nil ""))) "<h1>Hello World</h1>")
|
||||
true)
|
||||
;; appears in the list
|
||||
(host-bl-test "list shows created post"
|
||||
(contains? (dream-resp-body (host-bl-wapp (host-bl-send "GET" "/posts" nil ""))) "Hello World")
|
||||
true)
|
||||
;; create duplicate -> 409
|
||||
(host-bl-test "create duplicate -> 409"
|
||||
(dream-status (host-bl-wapp (host-bl-send "POST" "/posts" "Bearer good"
|
||||
"{\"slug\":\"hello\",\"title\":\"X\",\"body\":\"Y\"}")))
|
||||
409)
|
||||
;; missing fields -> 400
|
||||
(host-bl-test "create missing fields -> 400"
|
||||
(dream-status (host-bl-wapp (host-bl-send "POST" "/posts" "Bearer good" "{\"slug\":\"x\"}")))
|
||||
400)
|
||||
|
||||
;; update -> 200 and content changes
|
||||
(host-bl-test "update -> 200"
|
||||
(dream-status (host-bl-wapp (host-bl-send "PUT" "/posts/hello" "Bearer good"
|
||||
"{\"title\":\"Edited Title\",\"body\":\"Edited body.\"}")))
|
||||
200)
|
||||
(host-bl-test "update changed the rendered post"
|
||||
(contains? (dream-resp-body (host-bl-wapp (host-bl-send "GET" "/hello/" nil ""))) "<h1>Edited Title</h1>")
|
||||
true)
|
||||
(host-bl-test "update missing post -> 404"
|
||||
(dream-status (host-bl-wapp (host-bl-send "PUT" "/posts/ghost" "Bearer good"
|
||||
"{\"title\":\"T\",\"body\":\"B\"}")))
|
||||
404)
|
||||
(host-bl-test "update no auth -> 401"
|
||||
(dream-status (host-bl-wapp (host-bl-send "PUT" "/posts/hello" nil "{}")))
|
||||
401)
|
||||
|
||||
;; delete -> 200, then gone (404) and absent from list
|
||||
(host-bl-test "delete -> 200"
|
||||
(dream-status (host-bl-wapp (host-bl-send "DELETE" "/posts/hello" "Bearer good" "")))
|
||||
200)
|
||||
(host-bl-test "deleted post -> 404"
|
||||
(dream-status (host-bl-wapp (host-bl-send "GET" "/hello/" nil "")))
|
||||
404)
|
||||
(host-bl-test "deleted post gone from list"
|
||||
(contains? (dream-resp-body (host-bl-wapp (host-bl-send "GET" "/posts" nil ""))) "hello")
|
||||
false)
|
||||
(host-bl-test "delete missing -> 404"
|
||||
(dream-status (host-bl-wapp (host-bl-send "DELETE" "/posts/ghost" "Bearer good" "")))
|
||||
404)
|
||||
|
||||
(define
|
||||
host-bl-tests-run!
|
||||
(fn
|
||||
|
||||
Reference in New Issue
Block a user