Files
rose-ash/lib/gitea/tests/repo.sx
giles c037aca51f sx-gitea Phase 1: repo — forge core (owners, repo CRUD, per-repo git stores) + dream browse views (TDD, 91/91)
lib/gitea/repo.sx: forge handle over persist kv; owner principals
(user/org directory, identity-backed in Phase 2); repo records with
visibility/default-branch metadata; per-repo sx-git namespaces
(forge/<owner>/<name>) so delete is a prefix purge; ref resolution
(branch/tag/cid, annotated tags peeled) and tree-path navigation.

lib/gitea/web.sx: dream routes — repo index, repo home, branches,
tree/blob/raw browse at any ref, commit log, single-commit diff view,
JSON API for repo create/list/delete (201/400/409 semantics).

lib/gitea/tests/repo.sx (91 tests) + conformance.sh + scoreboard.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 12:45:05 +00:00

449 lines
13 KiB
Plaintext

; lib/gitea/tests/repo.sx — Phase 1: forge core (owners, repo CRUD, git
; wiring, ref/tree navigation) and the dream browse views + JSON API.
(define gitea-repo-pass 0)
(define gitea-repo-fail 0)
(define gitea-repo-fails (list))
; compare with = (structural), not equal? — map/filter-derived lists fail
; equal? against literals even when they print identically
(define
gitea-repo-test
(fn
(name actual expected)
(if
(= actual expected)
(set! gitea-repo-pass (+ gitea-repo-pass 1))
(begin
(set! gitea-repo-fail (+ gitea-repo-fail 1))
(set! gitea-repo-fails (append gitea-repo-fails (list {:name name :expected (inspect expected) :actual (inspect actual)})))))))
(define gt-db (persist/mem-backend))
(define gt-forge (gitea/forge gt-db))
; ── owners ───────────────────────────────────────────────────────────
(gitea-repo-test
"user-create returns user record"
(get (gitea/user-create! gt-forge "alice") :kind)
"user")
(gitea-repo-test
"org-create returns org record"
(get (gitea/org-create! gt-forge "acme") :kind)
"org")
(gitea-repo-test
"owner-get finds alice"
(get (gitea/owner-get gt-forge "alice") :name)
"alice")
(gitea-repo-test "owner-exists?" (gitea/owner-exists? gt-forge "alice") true)
(gitea-repo-test
"user? on user"
(gitea/user? (gitea/owner-get gt-forge "alice"))
true)
(gitea-repo-test
"org? on org"
(gitea/org? (gitea/owner-get gt-forge "acme"))
true)
(gitea-repo-test
"user? on org"
(gitea/user? (gitea/owner-get gt-forge "acme"))
false)
(gitea-repo-test
"duplicate owner conflicts"
(get (gitea/user-create! gt-forge "alice") :conflict)
true)
(gitea-repo-test
"owner name with slash rejected"
(get (gitea/user-create! gt-forge "a/b") :error)
"invalid-name")
(gitea-repo-test
"owner name empty rejected"
(get (gitea/user-create! gt-forge "") :error)
"invalid-name")
(gitea-repo-test
"reserved owner name rejected"
(get (gitea/user-create! gt-forge "api") :error)
"invalid-name")
(gitea-repo-test
"owners sorted"
(gitea/owners gt-forge)
(list "acme" "alice"))
; ── repo CRUD ────────────────────────────────────────────────────────
(define gt-rec (gitea/repo-create! gt-forge "alice" "proj" {:description "demo" :created-at 42}))
(gitea-repo-test "repo-create owner" (get gt-rec :owner) "alice")
(gitea-repo-test "repo-create name" (get gt-rec :name) "proj")
(gitea-repo-test
"repo-create default visibility"
(get gt-rec :visibility)
"public")
(gitea-repo-test
"repo-create default branch"
(get gt-rec :default-branch)
"main")
(gitea-repo-test
"repo-create keeps created-at"
(get gt-rec :created-at)
42)
(gitea-repo-test
"repo-get description"
(get (gitea/repo-get gt-forge "alice" "proj") :description)
"demo")
(gitea-repo-test
"repo-exists?"
(gitea/repo-exists? gt-forge "alice" "proj")
true)
(gitea-repo-test
"repo-get missing"
(gitea/repo-get gt-forge "alice" "nope")
nil)
(gitea-repo-test
"repo-create unknown owner"
(get (gitea/repo-create! gt-forge "bob" "x" {}) :error)
"no-such-owner")
(gitea-repo-test
"repo-create duplicate conflicts"
(get (gitea/repo-create! gt-forge "alice" "proj" {}) :conflict)
true)
(gitea-repo-test
"repo-create bad name"
(get (gitea/repo-create! gt-forge "alice" "ba d" {}) :error)
"invalid-name")
(gitea-repo-test
"repos lists alice/proj"
(gitea/repos gt-forge)
(list "alice/proj"))
(gitea/repo-create! gt-forge "acme" "proj" {:visibility "private"})
(gitea-repo-test
"same name under two owners"
(gitea/repos gt-forge)
(list "acme/proj" "alice/proj"))
(gitea-repo-test
"repos-for alice"
(gitea/repos-for gt-forge "alice")
(list "proj"))
(gitea-repo-test
"private visibility stored"
(get (gitea/repo-get gt-forge "acme" "proj") :visibility)
"private")
(gitea-repo-test
"repo-update! description"
(begin
(gitea/repo-update!
gt-forge
"alice"
"proj"
(fn (r) (assoc r :description "rewritten")))
(get (gitea/repo-get gt-forge "alice" "proj") :description))
"rewritten")
(gitea-repo-test
"repo-update! missing repo"
(gitea/repo-update! gt-forge "alice" "nope" (fn (r) r))
nil)
; ── git store wiring ─────────────────────────────────────────────────
(define gt-grepo (gitea/repo-git gt-forge "alice" "proj"))
(gitea-repo-test "new repo HEAD unborn" (git/head gt-grepo) nil)
(gitea-repo-test
"new repo HEAD targets main"
(git/head-target gt-grepo)
"heads/main")
(gitea-repo-test "new repo has no branches" (git/branches gt-grepo) (list))
(git/add! gt-grepo "README.md" "hello forge")
(git/add! gt-grepo "src/a.txt" "alpha\n")
(git/add! gt-grepo "src/b.txt" "beta\n")
(define gt-c1 (git/commit! gt-grepo {:message "init" :time 1 :author "alice"}))
(gitea-repo-test
"commit! advances main"
(git/branch-get gt-grepo "main")
gt-c1)
(git/add! gt-grepo "src/a.txt" "alpha2\n")
(define gt-c2 (git/commit! gt-grepo {:message "tweak a" :time 2 :author "alice"}))
(gitea-repo-test
"log newest first"
(git/log gt-grepo gt-c2)
(list gt-c2 gt-c1))
(gitea-repo-test "branches lists main" (git/branches gt-grepo) (list "main"))
(define gt-grepo2 (gitea/repo-git gt-forge "acme" "proj"))
(gitea-repo-test
"objects invisible across repos"
(git/has? gt-grepo2 gt-c1)
false)
; ── ref resolution ───────────────────────────────────────────────────
(gitea-repo-test "resolve branch" (gitea/resolve-ref gt-grepo "main") gt-c2)
(git/tag-lightweight! gt-grepo "v1")
(gitea-repo-test
"resolve lightweight tag"
(gitea/resolve-ref gt-grepo "v1")
gt-c2)
(git/tag! gt-grepo "v2" {:message "release" :time 3})
(gitea-repo-test
"resolve annotated tag peels to commit"
(gitea/resolve-ref gt-grepo "v2")
gt-c2)
(gitea-repo-test "resolve raw cid" (gitea/resolve-ref gt-grepo gt-c1) gt-c1)
(gitea-repo-test
"resolve unknown ref"
(gitea/resolve-ref gt-grepo "nope")
nil)
; ── tree navigation ──────────────────────────────────────────────────
(gitea-repo-test
"tree-at root is tree"
(get (gitea/tree-at gt-grepo gt-c2 "") :kind)
"tree")
(gitea-repo-test
"tree-at file is blob"
(get (gitea/tree-at gt-grepo gt-c2 "src/a.txt") :kind)
"blob")
(gitea-repo-test
"tree-at file cid matches content"
(get (gitea/tree-at gt-grepo gt-c2 "src/a.txt") :cid)
(git/cid (git/blob "alpha2\n")))
(gitea-repo-test
"tree-at dir is tree"
(get (gitea/tree-at gt-grepo gt-c2 "src") :kind)
"tree")
(gitea-repo-test
"tree-at missing path"
(gitea/tree-at gt-grepo gt-c2 "src/zzz")
nil)
(gitea-repo-test
"tree-at path through blob"
(gitea/tree-at gt-grepo gt-c2 "README.md/x")
nil)
(gitea-repo-test
"tree-at non-commit cid"
(gitea/tree-at gt-grepo (git/cid (git/blob "alpha2\n")) "")
nil)
; ── browse views ─────────────────────────────────────────────────────
(define gt-app (gitea/app gt-forge))
(define
gt-get
(fn (target) (gt-app (dream-request "GET" target {} ""))))
(define
gt-post
(fn (target body) (gt-app (dream-request "POST" target {} body))))
(define
gt-del
(fn (target) (gt-app (dream-request "DELETE" target {} ""))))
(gitea-repo-test "GET / status" (dream-status (gt-get "/")) 200)
(gitea-repo-test
"GET / lists repos"
(contains? (dream-resp-body (gt-get "/")) "alice/proj")
true)
(gitea-repo-test
"repo home status"
(dream-status (gt-get "/alice/proj"))
200)
(gitea-repo-test
"repo home shows description"
(contains? (dream-resp-body (gt-get "/alice/proj")) "rewritten")
true)
(gitea-repo-test
"repo home shows branch"
(contains? (dream-resp-body (gt-get "/alice/proj")) "main")
true)
(gitea-repo-test
"empty repo home"
(contains? (dream-resp-body (gt-get "/acme/proj")) "empty repository")
true)
(gitea-repo-test
"unknown repo 404"
(dream-status (gt-get "/nobody/none"))
404)
(gitea-repo-test
"branches page lists main"
(contains? (dream-resp-body (gt-get "/alice/proj/branches")) "main")
true)
(gitea-repo-test
"branches page unknown repo 404"
(dream-status (gt-get "/nobody/none/branches"))
404)
(gitea-repo-test
"tree root status"
(dream-status (gt-get "/alice/proj/tree/main"))
200)
(gitea-repo-test
"tree root lists src"
(contains? (dream-resp-body (gt-get "/alice/proj/tree/main")) "src")
true)
(gitea-repo-test
"tree root lists README"
(contains? (dream-resp-body (gt-get "/alice/proj/tree/main")) "README.md")
true)
(gitea-repo-test
"tree subdir lists a.txt"
(contains? (dream-resp-body (gt-get "/alice/proj/tree/main/src")) "a.txt")
true)
(gitea-repo-test
"tree at tag"
(dream-status (gt-get "/alice/proj/tree/v1"))
200)
(gitea-repo-test
"tree bad ref 404"
(dream-status (gt-get "/alice/proj/tree/nope"))
404)
(gitea-repo-test
"tree on blob path 404"
(dream-status (gt-get "/alice/proj/tree/main/README.md"))
404)
(gitea-repo-test
"blob status"
(dream-status (gt-get "/alice/proj/blob/main/src/a.txt"))
200)
(gitea-repo-test
"blob shows content"
(contains?
(dream-resp-body (gt-get "/alice/proj/blob/main/src/a.txt"))
"alpha2")
true)
(gitea-repo-test
"blob on tree path 404"
(dream-status (gt-get "/alice/proj/blob/main/src"))
404)
(gitea-repo-test
"raw body exact"
(dream-resp-body (gt-get "/alice/proj/raw/main/src/a.txt"))
"alpha2\n")
(gitea-repo-test
"raw missing file 404"
(dream-status (gt-get "/alice/proj/raw/main/zzz"))
404)
(gitea-repo-test
"commits status"
(dream-status (gt-get "/alice/proj/commits/main"))
200)
(gitea-repo-test
"commits show newest message"
(contains? (dream-resp-body (gt-get "/alice/proj/commits/main")) "tweak a")
true)
(gitea-repo-test
"commits show oldest message"
(contains? (dream-resp-body (gt-get "/alice/proj/commits/main")) "init")
true)
(gitea-repo-test
"commits bad ref 404"
(dream-status (gt-get "/alice/proj/commits/nope"))
404)
(gitea-repo-test
"commit view message"
(contains?
(dream-resp-body (gt-get (str "/alice/proj/commit/" gt-c2)))
"tweak a")
true)
(gitea-repo-test
"commit view diff content"
(contains?
(dream-resp-body (gt-get (str "/alice/proj/commit/" gt-c2)))
"alpha2")
true)
(gitea-repo-test
"root commit lists files"
(contains?
(dream-resp-body (gt-get (str "/alice/proj/commit/" gt-c1)))
"README.md")
true)
(gitea-repo-test
"commit bad cid 404"
(dream-status (gt-get "/alice/proj/commit/zzz"))
404)
; ── json api ─────────────────────────────────────────────────────────
(gitea-repo-test
"api repos json"
(dream-json-parse (dream-resp-body (gt-get "/api/repos")))
(list "acme/proj" "alice/proj"))
(gitea-repo-test
"api create 201"
(dream-status (gt-post "/api/repos" (dream-json-encode {:name "web" :owner "alice"})))
201)
(gitea-repo-test
"api create persisted"
(gitea/repo-exists? gt-forge "alice" "web")
true)
(gitea-repo-test
"api create duplicate 409"
(dream-status (gt-post "/api/repos" (dream-json-encode {:name "web" :owner "alice"})))
409)
(gitea-repo-test
"api create unknown owner 400"
(dream-status (gt-post "/api/repos" (dream-json-encode {:name "web" :owner "zeb"})))
400)
(gitea-repo-test
"api create bad name 400"
(dream-status (gt-post "/api/repos" (dream-json-encode {:name "b d" :owner "alice"})))
400)
(gitea-repo-test
"api delete 200"
(dream-status (gt-del "/api/repos/alice/web"))
200)
(gitea-repo-test
"api delete gone"
(gitea/repo-exists? gt-forge "alice" "web")
false)
(gitea-repo-test
"api delete missing 404"
(dream-status (gt-del "/api/repos/alice/web"))
404)
; ── delete purges the git namespace ──────────────────────────────────
(gitea/repo-create! gt-forge "alice" "tmp" {})
(define gt-gtmp (gitea/repo-git gt-forge "alice" "tmp"))
(git/add! gt-gtmp "f.txt" "data")
(git/commit! gt-gtmp {:message "x" :time 9})
(gitea-repo-test
"delete returns true"
(gitea/repo-delete! gt-forge "alice" "tmp")
true)
(gitea-repo-test
"delete removes record"
(gitea/repo-get gt-forge "alice" "tmp")
nil)
(gitea-repo-test
"delete purges git keys"
(len
(filter
(fn (k) (starts-with? k "forge/alice/tmp/"))
(persist/kv-keys gt-db)))
0)
(gitea-repo-test
"delete missing returns false"
(gitea/repo-delete! gt-forge "alice" "tmp")
false)
(gitea-repo-test
"other repos survive delete"
(gitea/repos gt-forge)
(list "acme/proj" "alice/proj"))