package spindle
import (
"bytes"
"encoding/json"
"net/http"
"net/http/httptest"
"strings"
"testing"
"time"
"github.com/bluesky-social/indigo/atproto/atcrypto"
"github.com/bluesky-social/indigo/atproto/auth"
"github.com/bluesky-social/indigo/atproto/identity"
"github.com/bluesky-social/indigo/atproto/syntax"
"tangled.org/core/api/org_tangled"
"tangled.org/core/idresolver"
"tangled.org/core/spindle/models"
)
// calls the spindle's own xrpc router as the repo owner, signed like the web signs
func (e *prPipelineEnv) asOwner(t *testing.T) func(nsid string, pipeline models.PipelineId) (int, string) {
t.Helper()
priv, err := atcrypto.GeneratePrivateKeyP256()
if err != nil {
t.Fatal(err)
}
pub, err := priv.PublicKey()
if err != nil {
t.Fatal(err)
}
resolver := e.n.s.res
e.n.s.res = idresolver.NewMockResolver(idresolver.MockDirectory{Ident: &identity.Identity{
DID: testOwner,
Keys: map[string]identity.VerificationMethod{"atproto": {Type: "Multikey", PublicKeyMultibase: pub.Multibase()}},
}})
router := e.n.s.XrpcRouter()
e.n.s.res = resolver
return func(nsid string, pipeline models.PipelineId) (int, string) {
t.Helper()
lxm := syntax.NSID(nsid)
token, err := auth.SignServiceAuth(testOwner, e.n.s.cfg.Server.Did().String(), time.Minute, &lxm, priv)
if err != nil {
t.Fatal(err)
}
body, _ := json.Marshal(map[string]string{"repo": testRepoDid.String(), "pipeline": string(pipeline)})
req := httptest.NewRequest(http.MethodPost, "/"+nsid, bytes.NewReader(body))
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
return w.Code, w.Body.String()
}
}
func TestRetryNeedsTheRunToHaveBeenApproved(t *testing.T) {
e := newPRPipelineEnv(t)
call := e.asOwner(t)
refused := func(what string, id models.PipelineId) {
t.Helper()
if code, body := call(org_tangled.CiRetryPipelineNSID, id); code != http.StatusBadRequest || !strings.Contains(body, "PipelineNotRetryable") {
t.Errorf("retrying %s = %d %s, want 400 PipelineNotRetryable", what, code, body)
}
}
ok := func(what, nsid string, id models.PipelineId) {
t.Helper()
if code, body := call(nsid, id); code != http.StatusOK {
t.Fatalf("%s = %d %s, want 200", what, code, body)
}
}
e.ticketAs(otherPull, "open", testDave, opCreate)
e.patchAs("bafydaveone", otherPull, e.head(t), testDave)
blocked := expectPRPipelines(t, e.n, 1)[0]
refused("a blocked run", blocked.ID)
ok("cancelling the blocked run", org_tangled.CiCancelPipelineNSID, blocked.ID)
refused("a blocked run cancelled before anyone approved it", blocked.ID)
e.patchAs("bafydavetwo", otherPull, e.head(t), testDave)
approved := expectPRPipelines(t, e.n, 2)[1]
ok("approving", org_tangled.CiApprovePipelineNSID, approved.ID)
ok("cancelling the approved run", org_tangled.CiCancelPipelineNSID, approved.ID)
ok("retrying an approved run that was cancelled", org_tangled.CiRetryPipelineNSID, approved.ID)
// a running pipeline can be retried. the new run goes beside it
e.ticketAs(pullRkey, "open", testBob, opCreate)
e.patchAs(patchOne, pullRkey, e.head(t), testBob)
running := expectPRPipelines(t, e.n, 4)[3]
if err := e.n.s.db.StatusRunning(models.WorkflowId{PipelineId: running.ID, Name: "pr.yaml"}, nil); err != nil {
t.Fatal(err)
}
ok("retrying a running run", org_tangled.CiRetryPipelineNSID, running.ID)
}