; lib/gitea/tests/repo.sx — Phase 1: forge core (owners, repo CRUD, git ; wiring, ref/tree navigation) and the dream browse views + JSON API. ; Mutating API calls authenticate as alice (Phase 2 gates them). (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" {}) (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 "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) (gitea/repo-create! gt-forge "alice" "hidden" {:visibility "private"}) (gitea-repo-test "private visibility stored" (get (gitea/repo-get gt-forge "alice" "hidden") :visibility) "private") ; ── 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 {} "")))) (gitea/token-create! gt-forge "alice" "tok-alice") (define gt-auth {:authorization "Bearer tok-alice"}) (define gt-posta (fn (target body) (gt-app (dream-request "POST" target gt-auth body)))) (define gt-dela (fn (target) (gt-app (dream-request "DELETE" target gt-auth "")))) (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 (as alice) ────────────────────────────────────────────── (gitea-repo-test "api repos json (anon: public only)" (dream-json-parse (dream-resp-body (gt-get "/api/repos"))) (list "acme/proj" "alice/proj")) (gitea-repo-test "api create 201" (dream-status (gt-posta "/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-posta "/api/repos" (dream-json-encode {:name "web" :owner "alice"}))) 409) (gitea-repo-test "api create unknown owner 400" (dream-status (gt-posta "/api/repos" (dream-json-encode {:name "web" :owner "zeb"}))) 400) (gitea-repo-test "api create bad name 400" (dream-status (gt-posta "/api/repos" (dream-json-encode {:name "b d" :owner "alice"}))) 400) (gitea-repo-test "api delete 200" (dream-status (gt-dela "/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-dela "/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/hidden" "alice/proj"))