lib/gitea/pr.sx: PRs as kv records sharing the per-repo number counter with issues. Diffs are LIVE, computed from the merge base of the current branch heads to the source head via sx-git (no spurious deletions when the target moves on). Reviews: latest verdict per reviewer wins; authors cannot review their own PR; approved? = some approve and no outstanding request-changes. Lifecycle is a lib/flow durable workflow (deterministic-replay suspend): open -(approval)-> approved -(merge)-> merged; review! resumes the approval suspend when the verdict set first approves, merge! resumes the rest, close! cancels, reopen! starts a fresh flow. The flow env lives in the forge handle; the record's :state stays the source of truth. Merge via git/merge-commits over the merge base: up-to-date, fast- forward (ref move only), true two-parent merge commit, or conflicts with the conflicting paths. Every ref move is branch-cas! — concurrent pushes surface as 'stale'. Merge queue: approved PRs merge in order, failures stay queued. Web: pulls list + PR page (body html, reviews, lifecycle, unified diff), JSON API for create/review/merge (409 on conflicts/stale)/close (author or write)/enqueue/queue-process. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
866 lines
20 KiB
Plaintext
866 lines
20 KiB
Plaintext
; lib/gitea/tests/pr.sx — Phase 5: PR records, live merge-base diffs,
|
|
; review threads (latest verdict per reviewer), the durable flow
|
|
; lifecycle, all four merge shapes (merge/ff/up-to-date/conflicts), the
|
|
; merge queue, and the PR web routes + JSON API.
|
|
|
|
(st-bootstrap-classes!)
|
|
(content/bootstrap!)
|
|
(content-bootstrap-markdown!)
|
|
(content-bootstrap-table!)
|
|
|
|
(define gitea-pr-pass 0)
|
|
(define gitea-pr-fail 0)
|
|
(define gitea-pr-fails (list))
|
|
|
|
(define
|
|
gitea-pr-test
|
|
(fn
|
|
(name actual expected)
|
|
(if
|
|
(= actual expected)
|
|
(set! gitea-pr-pass (+ gitea-pr-pass 1))
|
|
(begin
|
|
(set! gitea-pr-fail (+ gitea-pr-fail 1))
|
|
(set! gitea-pr-fails (append gitea-pr-fails (list {:name name :expected (inspect expected) :actual (inspect actual)})))))))
|
|
|
|
; ── setup: repo with diverged branches ───────────────────────────────
|
|
|
|
(define gp-db (persist/mem-backend))
|
|
(define gp-forge (gitea/forge gp-db))
|
|
(gitea/user-create! gp-forge "alice")
|
|
(gitea/user-create! gp-forge "bob")
|
|
(gitea/user-create! gp-forge "carol")
|
|
(gitea/user-create! gp-forge "eve")
|
|
(gitea/repo-create! gp-forge "alice" "proj" {})
|
|
(gitea/repo-create! gp-forge "alice" "sec" {:visibility "private"})
|
|
(gitea/token-create! gp-forge "alice" "tok-a")
|
|
(gitea/token-create! gp-forge "bob" "tok-b")
|
|
(gitea/token-create! gp-forge "carol" "tok-c")
|
|
(gitea/token-create! gp-forge "eve" "tok-e")
|
|
|
|
(define gp-g (gitea/repo-git gp-forge "alice" "proj"))
|
|
(git/add! gp-g "README.md" "base\n")
|
|
(git/add! gp-g "lib.txt" "lib\n")
|
|
(define gp-c1 (git/commit! gp-g {:message "c1" :time 1 :author "alice"}))
|
|
(git/branch! gp-g "feat")
|
|
(git/checkout! gp-g "feat")
|
|
(git/add! gp-g "feature.txt" "feature line\n")
|
|
(define gp-c2 (git/commit! gp-g {:message "c2 feature" :time 2 :author "bob"}))
|
|
(git/checkout! gp-g "main")
|
|
(git/add! gp-g "other.txt" "other\n")
|
|
(define gp-c3 (git/commit! gp-g {:message "c3 other" :time 3 :author "alice"}))
|
|
|
|
; ── create / validate ────────────────────────────────────────────────
|
|
|
|
(define
|
|
gp-pr1
|
|
(gitea/pr-create!
|
|
gp-forge
|
|
"alice"
|
|
"proj"
|
|
"bob"
|
|
"Add feature"
|
|
"feat"
|
|
"main"
|
|
"Adds a *feature*."
|
|
{:created-at 5}))
|
|
|
|
(gitea-pr-test "pr number" (get gp-pr1 :number) 1)
|
|
(gitea-pr-test "pr state" (get gp-pr1 :state) "open")
|
|
(gitea-pr-test "pr source" (get gp-pr1 :source) "feat")
|
|
(gitea-pr-test "pr target" (get gp-pr1 :target) "main")
|
|
(gitea-pr-test "pr has flow id" (nil? (get gp-pr1 :flow-id)) false)
|
|
(gitea-pr-test
|
|
"flow starts at review"
|
|
(gitea/pr-flow-status gp-forge gp-pr1)
|
|
"review")
|
|
|
|
(gitea-pr-test
|
|
"unknown source"
|
|
(get
|
|
(gitea/pr-create!
|
|
gp-forge
|
|
"alice"
|
|
"proj"
|
|
"bob"
|
|
"t"
|
|
"nope"
|
|
"main"
|
|
""
|
|
{})
|
|
:error)
|
|
"no-such-source")
|
|
(gitea-pr-test
|
|
"unknown target"
|
|
(get
|
|
(gitea/pr-create!
|
|
gp-forge
|
|
"alice"
|
|
"proj"
|
|
"bob"
|
|
"t"
|
|
"feat"
|
|
"nope"
|
|
""
|
|
{})
|
|
:error)
|
|
"no-such-target")
|
|
(gitea-pr-test
|
|
"same branch"
|
|
(get
|
|
(gitea/pr-create!
|
|
gp-forge
|
|
"alice"
|
|
"proj"
|
|
"bob"
|
|
"t"
|
|
"main"
|
|
"main"
|
|
""
|
|
{})
|
|
:error)
|
|
"same-branch")
|
|
(gitea-pr-test
|
|
"empty title"
|
|
(get
|
|
(gitea/pr-create!
|
|
gp-forge
|
|
"alice"
|
|
"proj"
|
|
"bob"
|
|
""
|
|
"feat"
|
|
"main"
|
|
""
|
|
{})
|
|
:error)
|
|
"empty-title")
|
|
(gitea-pr-test
|
|
"missing repo"
|
|
(get
|
|
(gitea/pr-create!
|
|
gp-forge
|
|
"alice"
|
|
"none"
|
|
"bob"
|
|
"t"
|
|
"feat"
|
|
"main"
|
|
""
|
|
{})
|
|
:error)
|
|
"no-such-repo")
|
|
(gitea-pr-test
|
|
"missing author"
|
|
(get
|
|
(gitea/pr-create!
|
|
gp-forge
|
|
"alice"
|
|
"proj"
|
|
"zeb"
|
|
"t"
|
|
"feat"
|
|
"main"
|
|
""
|
|
{})
|
|
:error)
|
|
"no-such-user")
|
|
|
|
(gitea-pr-test
|
|
"prs list"
|
|
(gitea/prs gp-forge "alice" "proj")
|
|
(list 1))
|
|
|
|
; numbers are shared with issues
|
|
(gitea-pr-test
|
|
"shared counter with issues"
|
|
(get
|
|
(gitea/issue-create!
|
|
gp-forge
|
|
"alice"
|
|
"proj"
|
|
"alice"
|
|
"An issue"
|
|
""
|
|
{})
|
|
:number)
|
|
2)
|
|
|
|
; ── live diff against the merge base ─────────────────────────────────
|
|
|
|
(gitea-pr-test
|
|
"diff added"
|
|
(get (gitea/pr-diff gp-forge "alice" "proj" 1) :added)
|
|
(list "feature.txt"))
|
|
(gitea-pr-test
|
|
"diff no spurious deletions"
|
|
(get (gitea/pr-diff gp-forge "alice" "proj" 1) :deleted)
|
|
(list))
|
|
(gitea-pr-test
|
|
"diff unified shows addition"
|
|
(contains?
|
|
(gitea/pr-diff-unified gp-forge "alice" "proj" 1)
|
|
"+feature line")
|
|
true)
|
|
(gitea-pr-test
|
|
"diff of missing pr"
|
|
(gitea/pr-diff gp-forge "alice" "proj" 99)
|
|
nil)
|
|
|
|
; ── reviews ──────────────────────────────────────────────────────────
|
|
|
|
(gitea/pr-review!
|
|
gp-forge
|
|
"alice"
|
|
"proj"
|
|
1
|
|
"carol"
|
|
"request-changes"
|
|
"needs tests"
|
|
{:at 6})
|
|
(gitea-pr-test
|
|
"changes requested blocks approval"
|
|
(gitea/pr-approved? (gitea/pr-get gp-forge "alice" "proj" 1))
|
|
false)
|
|
(gitea-pr-test
|
|
"flow still at review"
|
|
(gitea/pr-flow-status
|
|
gp-forge
|
|
(gitea/pr-get gp-forge "alice" "proj" 1))
|
|
"review")
|
|
|
|
(gitea-pr-test
|
|
"author cannot review own pr"
|
|
(get
|
|
(gitea/pr-review!
|
|
gp-forge
|
|
"alice"
|
|
"proj"
|
|
1
|
|
"bob"
|
|
"approve"
|
|
""
|
|
{})
|
|
:error)
|
|
"own-pr")
|
|
(gitea-pr-test
|
|
"invalid verdict"
|
|
(get
|
|
(gitea/pr-review!
|
|
gp-forge
|
|
"alice"
|
|
"proj"
|
|
1
|
|
"carol"
|
|
"meh"
|
|
""
|
|
{})
|
|
:error)
|
|
"invalid-verdict")
|
|
(gitea-pr-test
|
|
"unknown reviewer"
|
|
(get
|
|
(gitea/pr-review!
|
|
gp-forge
|
|
"alice"
|
|
"proj"
|
|
1
|
|
"zeb"
|
|
"approve"
|
|
""
|
|
{})
|
|
:error)
|
|
"no-such-user")
|
|
(gitea-pr-test
|
|
"review missing pr"
|
|
(get
|
|
(gitea/pr-review!
|
|
gp-forge
|
|
"alice"
|
|
"proj"
|
|
99
|
|
"carol"
|
|
"approve"
|
|
""
|
|
{})
|
|
:error)
|
|
"no-such-pr")
|
|
|
|
(gitea/pr-review!
|
|
gp-forge
|
|
"alice"
|
|
"proj"
|
|
1
|
|
"carol"
|
|
"approve"
|
|
"looks good now"
|
|
{:at 7})
|
|
(gitea-pr-test
|
|
"latest verdict wins"
|
|
(gitea/pr-approved? (gitea/pr-get gp-forge "alice" "proj" 1))
|
|
true)
|
|
(gitea-pr-test
|
|
"reviews accumulate"
|
|
(len (get (gitea/pr-get gp-forge "alice" "proj" 1) :reviews))
|
|
2)
|
|
(gitea-pr-test
|
|
"flow advanced to approved"
|
|
(gitea/pr-flow-status
|
|
gp-forge
|
|
(gitea/pr-get gp-forge "alice" "proj" 1))
|
|
"approved")
|
|
|
|
; ── merge: true 3-way ────────────────────────────────────────────────
|
|
|
|
(gitea-pr-test
|
|
"unapproved merge rejected"
|
|
(get
|
|
(gitea/pr-merge! gp-forge "alice" "proj" 2 "alice" {})
|
|
:error)
|
|
"no-such-pr")
|
|
|
|
(define
|
|
gp-m1
|
|
(gitea/pr-merge! gp-forge "alice" "proj" 1 "alice" {:time 8}))
|
|
|
|
(gitea-pr-test "merge state" (get gp-m1 :state) "merged")
|
|
(gitea-pr-test "merge cid recorded" (nil? (get gp-m1 :merge-cid)) false)
|
|
(gitea-pr-test
|
|
"main moved to merge commit"
|
|
(git/branch-get gp-g "main")
|
|
(get gp-m1 :merge-cid))
|
|
(gitea-pr-test
|
|
"merge commit has two parents"
|
|
(git/commit-parents (git/read gp-g (get gp-m1 :merge-cid)))
|
|
(list gp-c3 gp-c2))
|
|
(gitea-pr-test
|
|
"merged tree keeps target file"
|
|
(get (gitea/tree-at gp-g (get gp-m1 :merge-cid) "other.txt") :kind)
|
|
"blob")
|
|
(gitea-pr-test
|
|
"merged tree gains source file"
|
|
(get (gitea/tree-at gp-g (get gp-m1 :merge-cid) "feature.txt") :kind)
|
|
"blob")
|
|
(gitea-pr-test
|
|
"flow reports merged"
|
|
(gitea/pr-flow-status
|
|
gp-forge
|
|
(gitea/pr-get gp-forge "alice" "proj" 1))
|
|
"merged")
|
|
(gitea-pr-test
|
|
"merge twice rejected"
|
|
(get
|
|
(gitea/pr-merge! gp-forge "alice" "proj" 1 "alice" {})
|
|
:error)
|
|
"not-open")
|
|
(gitea-pr-test
|
|
"review after merge rejected"
|
|
(get
|
|
(gitea/pr-review!
|
|
gp-forge
|
|
"alice"
|
|
"proj"
|
|
1
|
|
"carol"
|
|
"approve"
|
|
""
|
|
{})
|
|
:error)
|
|
"not-open")
|
|
|
|
; ── merge: fast-forward ──────────────────────────────────────────────
|
|
|
|
(git/checkout! gp-g "main")
|
|
(git/branch! gp-g "hot")
|
|
(git/checkout! gp-g "hot")
|
|
(git/add! gp-g "hotfix.txt" "fix\n")
|
|
(define gp-c4 (git/commit! gp-g {:message "hotfix" :time 9 :author "bob"}))
|
|
(git/checkout! gp-g "main")
|
|
|
|
(define
|
|
gp-pr3
|
|
(gitea/pr-create!
|
|
gp-forge
|
|
"alice"
|
|
"proj"
|
|
"bob"
|
|
"Hotfix"
|
|
"hot"
|
|
"main"
|
|
""
|
|
{}))
|
|
(gitea-pr-test "pr3 number" (get gp-pr3 :number) 3)
|
|
(gitea/pr-review!
|
|
gp-forge
|
|
"alice"
|
|
"proj"
|
|
3
|
|
"carol"
|
|
"approve"
|
|
""
|
|
{})
|
|
(define
|
|
gp-m3
|
|
(gitea/pr-merge! gp-forge "alice" "proj" 3 "alice" {}))
|
|
(gitea-pr-test "ff merge state" (get gp-m3 :state) "merged")
|
|
(gitea-pr-test
|
|
"ff moves main to source head"
|
|
(git/branch-get gp-g "main")
|
|
gp-c4)
|
|
(gitea-pr-test "ff merge-cid is source head" (get gp-m3 :merge-cid) gp-c4)
|
|
|
|
; ── merge: up-to-date ────────────────────────────────────────────────
|
|
|
|
(git/checkout! gp-g "main")
|
|
(git/branch! gp-g "same")
|
|
(define
|
|
gp-pr4
|
|
(gitea/pr-create!
|
|
gp-forge
|
|
"alice"
|
|
"proj"
|
|
"bob"
|
|
"No-op"
|
|
"same"
|
|
"main"
|
|
""
|
|
{}))
|
|
(gitea/pr-review!
|
|
gp-forge
|
|
"alice"
|
|
"proj"
|
|
4
|
|
"carol"
|
|
"approve"
|
|
""
|
|
{})
|
|
(define
|
|
gp-m4
|
|
(gitea/pr-merge! gp-forge "alice" "proj" 4 "alice" {}))
|
|
(gitea-pr-test "up-to-date merge state" (get gp-m4 :state) "merged")
|
|
(gitea-pr-test "up-to-date leaves main" (git/branch-get gp-g "main") gp-c4)
|
|
|
|
; ── merge: conflicts ─────────────────────────────────────────────────
|
|
|
|
(git/checkout! gp-g "main")
|
|
(git/branch! gp-g "conf")
|
|
(git/checkout! gp-g "conf")
|
|
(git/add! gp-g "lib.txt" "conf version\n")
|
|
(git/commit! gp-g {:message "conf change" :time 10 :author "bob"})
|
|
(git/checkout! gp-g "main")
|
|
(git/add! gp-g "lib.txt" "main version\n")
|
|
(git/commit! gp-g {:message "main change" :time 11 :author "alice"})
|
|
|
|
(define
|
|
gp-pr5
|
|
(gitea/pr-create!
|
|
gp-forge
|
|
"alice"
|
|
"proj"
|
|
"bob"
|
|
"Conflicting"
|
|
"conf"
|
|
"main"
|
|
""
|
|
{}))
|
|
(gitea/pr-review!
|
|
gp-forge
|
|
"alice"
|
|
"proj"
|
|
5
|
|
"carol"
|
|
"approve"
|
|
""
|
|
{})
|
|
(define
|
|
gp-m5
|
|
(gitea/pr-merge! gp-forge "alice" "proj" 5 "alice" {}))
|
|
(gitea-pr-test "conflict merge errors" (get gp-m5 :error) "conflicts")
|
|
(gitea-pr-test "conflict paths" (get gp-m5 :conflicts) (list "lib.txt"))
|
|
(gitea-pr-test
|
|
"conflict leaves pr open"
|
|
(get (gitea/pr-get gp-forge "alice" "proj" 5) :state)
|
|
"open")
|
|
(gitea-pr-test
|
|
"conflict leaves flow at approved"
|
|
(gitea/pr-flow-status
|
|
gp-forge
|
|
(gitea/pr-get gp-forge "alice" "proj" 5))
|
|
"approved")
|
|
|
|
; ── close / reopen ───────────────────────────────────────────────────
|
|
|
|
(gitea/pr-close! gp-forge "alice" "proj" 5)
|
|
(gitea-pr-test
|
|
"close state"
|
|
(get (gitea/pr-get gp-forge "alice" "proj" 5) :state)
|
|
"closed")
|
|
(gitea-pr-test
|
|
"close cancels flow"
|
|
(gitea/pr-flow-status
|
|
gp-forge
|
|
(gitea/pr-get gp-forge "alice" "proj" 5))
|
|
"closed")
|
|
(gitea-pr-test
|
|
"merge closed pr rejected"
|
|
(get
|
|
(gitea/pr-merge! gp-forge "alice" "proj" 5 "alice" {})
|
|
:error)
|
|
"not-open")
|
|
(gitea-pr-test
|
|
"close twice"
|
|
(gitea/pr-close! gp-forge "alice" "proj" 5)
|
|
nil)
|
|
|
|
(gitea/pr-reopen! gp-forge "alice" "proj" 5)
|
|
(gitea-pr-test
|
|
"reopen state"
|
|
(get (gitea/pr-get gp-forge "alice" "proj" 5) :state)
|
|
"open")
|
|
(gitea-pr-test
|
|
"reopen restarts lifecycle"
|
|
(gitea/pr-flow-status
|
|
gp-forge
|
|
(gitea/pr-get gp-forge "alice" "proj" 5))
|
|
"review")
|
|
|
|
; ── merge queue ──────────────────────────────────────────────────────
|
|
|
|
(git/checkout! gp-g "main")
|
|
(git/branch! gp-g "q1")
|
|
(git/checkout! gp-g "q1")
|
|
(git/add! gp-g "q1.txt" "one\n")
|
|
(git/commit! gp-g {:message "q1" :time 12 :author "bob"})
|
|
(git/checkout! gp-g "main")
|
|
(git/branch! gp-g "q2")
|
|
(git/checkout! gp-g "q2")
|
|
(git/add! gp-g "q2.txt" "two\n")
|
|
(git/commit! gp-g {:message "q2" :time 13 :author "bob"})
|
|
(git/checkout! gp-g "main")
|
|
(git/branch! gp-g "q3")
|
|
(git/checkout! gp-g "q3")
|
|
(git/add! gp-g "q3.txt" "three\n")
|
|
(git/commit! gp-g {:message "q3" :time 14 :author "bob"})
|
|
(git/checkout! gp-g "main")
|
|
|
|
(define
|
|
gp-pr6
|
|
(gitea/pr-create!
|
|
gp-forge
|
|
"alice"
|
|
"proj"
|
|
"bob"
|
|
"Queue one"
|
|
"q1"
|
|
"main"
|
|
""
|
|
{}))
|
|
(define
|
|
gp-pr7
|
|
(gitea/pr-create!
|
|
gp-forge
|
|
"alice"
|
|
"proj"
|
|
"bob"
|
|
"Queue two"
|
|
"q2"
|
|
"main"
|
|
""
|
|
{}))
|
|
(define
|
|
gp-pr8
|
|
(gitea/pr-create!
|
|
gp-forge
|
|
"alice"
|
|
"proj"
|
|
"bob"
|
|
"Queue three"
|
|
"q3"
|
|
"main"
|
|
""
|
|
{}))
|
|
(gitea/pr-review!
|
|
gp-forge
|
|
"alice"
|
|
"proj"
|
|
6
|
|
"carol"
|
|
"approve"
|
|
""
|
|
{})
|
|
(gitea/pr-review!
|
|
gp-forge
|
|
"alice"
|
|
"proj"
|
|
7
|
|
"carol"
|
|
"approve"
|
|
""
|
|
{})
|
|
; pr8 stays unapproved; pr5 (reopened, approved, conflicting) joins the queue
|
|
|
|
(gitea-pr-test
|
|
"queue starts empty"
|
|
(gitea/queue gp-forge "alice" "proj")
|
|
(list))
|
|
(gitea-pr-test
|
|
"queue rejects unapproved"
|
|
(get (gitea/queue-add! gp-forge "alice" "proj" 8) :error)
|
|
"not-approved")
|
|
(gitea-pr-test
|
|
"queue rejects missing"
|
|
(get (gitea/queue-add! gp-forge "alice" "proj" 99) :error)
|
|
"no-such-pr")
|
|
|
|
(gitea/queue-add! gp-forge "alice" "proj" 6)
|
|
(gitea/queue-add! gp-forge "alice" "proj" 6)
|
|
(gitea/queue-add! gp-forge "alice" "proj" 7)
|
|
(gitea/queue-add! gp-forge "alice" "proj" 5)
|
|
(gitea-pr-test
|
|
"queue dedups"
|
|
(gitea/queue gp-forge "alice" "proj")
|
|
(list 6 7 5))
|
|
|
|
(define gp-qres (gitea/queue-process! gp-forge "alice" "proj" "alice"))
|
|
(gitea-pr-test "queue processed all" (len gp-qres) 3)
|
|
(gitea-pr-test
|
|
"queue pr6 merged"
|
|
(get (nth gp-qres 0) :merged)
|
|
true)
|
|
(gitea-pr-test
|
|
"queue pr7 merged"
|
|
(get (nth gp-qres 1) :merged)
|
|
true)
|
|
(gitea-pr-test
|
|
"queue pr5 conflicts"
|
|
(get (nth gp-qres 2) :error)
|
|
"conflicts")
|
|
(gitea-pr-test
|
|
"failures stay queued"
|
|
(gitea/queue gp-forge "alice" "proj")
|
|
(list 5))
|
|
(gitea-pr-test
|
|
"pr6 state merged"
|
|
(get (gitea/pr-get gp-forge "alice" "proj" 6) :state)
|
|
"merged")
|
|
(gitea-pr-test
|
|
"pr7 state merged"
|
|
(get (gitea/pr-get gp-forge "alice" "proj" 7) :state)
|
|
"merged")
|
|
(gitea-pr-test
|
|
"main has both queue files"
|
|
(get (gitea/tree-at gp-g (git/branch-get gp-g "main") "q2.txt") :kind)
|
|
"blob")
|
|
|
|
(gitea/queue-remove! gp-forge "alice" "proj" 5)
|
|
(gitea-pr-test "queue-remove!" (gitea/queue gp-forge "alice" "proj") (list))
|
|
|
|
; ── web routes ───────────────────────────────────────────────────────
|
|
|
|
(define gp-app (gitea/app gp-forge))
|
|
(define gp-hdr (fn (tok) (if (nil? tok) {} {:authorization (str "Bearer " tok)})))
|
|
(define
|
|
gp-get
|
|
(fn (target tok) (gp-app (dream-request "GET" target (gp-hdr tok) ""))))
|
|
(define
|
|
gp-post
|
|
(fn
|
|
(target tok body)
|
|
(gp-app (dream-request "POST" target (gp-hdr tok) body))))
|
|
|
|
(gitea-pr-test
|
|
"pulls page 200"
|
|
(dream-status (gp-get "/alice/proj/pulls" nil))
|
|
200)
|
|
(gitea-pr-test
|
|
"pulls page shows title"
|
|
(contains?
|
|
(dream-resp-body (gp-get "/alice/proj/pulls" nil))
|
|
"Add feature")
|
|
true)
|
|
(gitea-pr-test
|
|
"pulls page shows state"
|
|
(contains? (dream-resp-body (gp-get "/alice/proj/pulls" nil)) "[merged]")
|
|
true)
|
|
|
|
(gitea-pr-test
|
|
"pull page 200"
|
|
(dream-status (gp-get "/alice/proj/pulls/1" nil))
|
|
200)
|
|
(gitea-pr-test
|
|
"pull page shows branches"
|
|
(contains?
|
|
(dream-resp-body (gp-get "/alice/proj/pulls/1" nil))
|
|
"feat -> main")
|
|
true)
|
|
(gitea-pr-test
|
|
"pull page renders body"
|
|
(contains? (dream-resp-body (gp-get "/alice/proj/pulls/1" nil)) "<p>")
|
|
true)
|
|
(gitea-pr-test
|
|
"pull page shows review verdict"
|
|
(contains?
|
|
(dream-resp-body (gp-get "/alice/proj/pulls/1" nil))
|
|
"carol: approve")
|
|
true)
|
|
(gitea-pr-test
|
|
"pull page shows lifecycle"
|
|
(contains? (dream-resp-body (gp-get "/alice/proj/pulls/1" nil)) "merged")
|
|
true)
|
|
(gitea-pr-test
|
|
"pull page bad number 404"
|
|
(dream-status (gp-get "/alice/proj/pulls/abc" nil))
|
|
404)
|
|
(gitea-pr-test
|
|
"pull page missing 404"
|
|
(dream-status (gp-get "/alice/proj/pulls/99" nil))
|
|
404)
|
|
(gitea-pr-test
|
|
"private pulls anon 404"
|
|
(dream-status (gp-get "/alice/sec/pulls" nil))
|
|
404)
|
|
|
|
(gitea-pr-test
|
|
"api pulls len"
|
|
(len
|
|
(dream-json-parse
|
|
(dream-resp-body (gp-get "/api/repos/alice/proj/pulls" nil))))
|
|
7)
|
|
(gitea-pr-test
|
|
"api pulls first source"
|
|
(get
|
|
(first
|
|
(dream-json-parse
|
|
(dream-resp-body (gp-get "/api/repos/alice/proj/pulls" nil))))
|
|
:source)
|
|
"feat")
|
|
|
|
(gitea-pr-test
|
|
"api create anon 401"
|
|
(dream-status
|
|
(gp-post "/api/repos/alice/proj/pulls" nil (dream-json-encode {:source "q3" :title "t" :target "main"})))
|
|
401)
|
|
(gitea-pr-test
|
|
"api create 201"
|
|
(dream-status
|
|
(gp-post
|
|
"/api/repos/alice/proj/pulls"
|
|
"tok-e"
|
|
(dream-json-encode {:source "q3" :title "Eve PR" :body "please" :target "main"})))
|
|
201)
|
|
(gitea-pr-test
|
|
"api create bad source 400"
|
|
(dream-status
|
|
(gp-post
|
|
"/api/repos/alice/proj/pulls"
|
|
"tok-e"
|
|
(dream-json-encode {:source "zz" :title "t" :target "main"})))
|
|
400)
|
|
|
|
; eve's PR is #9
|
|
(gitea-pr-test
|
|
"api review 200"
|
|
(dream-status
|
|
(gp-post
|
|
"/api/repos/alice/proj/pulls/9/reviews"
|
|
"tok-c"
|
|
(dream-json-encode {:verdict "approve" :body "ok"})))
|
|
200)
|
|
(gitea-pr-test
|
|
"api self-review 400"
|
|
(dream-status
|
|
(gp-post
|
|
"/api/repos/alice/proj/pulls/9/reviews"
|
|
"tok-e"
|
|
(dream-json-encode {:verdict "approve"})))
|
|
400)
|
|
(gitea-pr-test
|
|
"api review anon 401"
|
|
(dream-status
|
|
(gp-post
|
|
"/api/repos/alice/proj/pulls/9/reviews"
|
|
nil
|
|
(dream-json-encode {:verdict "approve"})))
|
|
401)
|
|
(gitea-pr-test
|
|
"api review missing pr 404"
|
|
(dream-status
|
|
(gp-post
|
|
"/api/repos/alice/proj/pulls/99/reviews"
|
|
"tok-c"
|
|
(dream-json-encode {:verdict "approve"})))
|
|
404)
|
|
|
|
(gitea-pr-test
|
|
"api merge reader 403"
|
|
(dream-status (gp-post "/api/repos/alice/proj/pulls/9/merge" "tok-e" "{}"))
|
|
403)
|
|
(gitea-pr-test
|
|
"api merge anon 401"
|
|
(dream-status (gp-post "/api/repos/alice/proj/pulls/9/merge" nil "{}"))
|
|
401)
|
|
(gitea-pr-test
|
|
"api merge write 200"
|
|
(dream-status (gp-post "/api/repos/alice/proj/pulls/9/merge" "tok-a" "{}"))
|
|
200)
|
|
(gitea-pr-test
|
|
"api merge applied"
|
|
(get (gitea/pr-get gp-forge "alice" "proj" 9) :state)
|
|
"merged")
|
|
|
|
; reopened conflicting pr 5 still conflicts over the api
|
|
(gitea-pr-test
|
|
"api merge conflict 409"
|
|
(dream-status (gp-post "/api/repos/alice/proj/pulls/5/merge" "tok-a" "{}"))
|
|
409)
|
|
|
|
; eve authors #10 and may close it herself; carol (reader) may not
|
|
(gp-post "/api/repos/alice/proj/pulls" "tok-e" (dream-json-encode {:source "conf" :title "To close" :target "main"}))
|
|
(gitea-pr-test
|
|
"api close by reader 403"
|
|
(dream-status (gp-post "/api/repos/alice/proj/pulls/10/close" "tok-c" "{}"))
|
|
403)
|
|
(gitea-pr-test
|
|
"api close by author 200"
|
|
(dream-status (gp-post "/api/repos/alice/proj/pulls/10/close" "tok-e" "{}"))
|
|
200)
|
|
(gitea-pr-test
|
|
"api close applied"
|
|
(get (gitea/pr-get gp-forge "alice" "proj" 10) :state)
|
|
"closed")
|
|
(gitea-pr-test
|
|
"api close again 409"
|
|
(dream-status (gp-post "/api/repos/alice/proj/pulls/10/close" "tok-e" "{}"))
|
|
409)
|
|
|
|
; queue over the api: pr5 is approved (reviews survive reopen)
|
|
(gitea-pr-test
|
|
"api enqueue reader 403"
|
|
(dream-status
|
|
(gp-post "/api/repos/alice/proj/pulls/5/enqueue" "tok-e" "{}"))
|
|
403)
|
|
(gitea-pr-test
|
|
"api enqueue 200"
|
|
(dream-status
|
|
(gp-post "/api/repos/alice/proj/pulls/5/enqueue" "tok-a" "{}"))
|
|
200)
|
|
(gitea-pr-test
|
|
"api queue json"
|
|
(dream-json-parse
|
|
(dream-resp-body (gp-get "/api/repos/alice/proj/merge-queue" nil)))
|
|
(list 5))
|
|
(gitea-pr-test
|
|
"api queue process 200"
|
|
(dream-status
|
|
(gp-post "/api/repos/alice/proj/merge-queue/process" "tok-a" "{}"))
|
|
200)
|
|
(gitea-pr-test
|
|
"api queue process reports conflict"
|
|
(get
|
|
(first
|
|
(dream-json-parse
|
|
(dream-resp-body
|
|
(gp-post "/api/repos/alice/proj/merge-queue/process" "tok-a" "{}"))))
|
|
:error)
|
|
"conflicts")
|