package main
import (
"net/http"
"net/http/httptest"
"strings"
"testing"
"time"
"craftdesign.group/quarry/internal/oltp"
)
// A RUN TOKEN'S LOGIN OPENS THE API DOOR AND THE SYNC LANE FOR ITS ONE
// REPOSITORY, WITH NO ACCOUNT BEHIND IT, UNTIL ITS RUN ENDS (RFC 0021
// §runs). Its letters are the run's: contents: write reaches the op
// needing i; a read-only one is refused it.
func TestARunLoginOpensItsRepositoryUntilItsRunEnds(t *testing.T) {
g := newDoorRig(t)
db := g.rg.odb
db.SyncProjectCode = func(string) (string, bool) { return "CODE", true }
for _, id := range []string{"r1", "r2"} {
if err := db.AddCIRun(oltp.CIRun{RunID: id, Repo: "vault", Checkin: "c", Workflow: "w", Pipeline: "p", Status: "running"}); err != nil {
t.Fatal(err)
}
}
mint := func(run, letters string) (string, string) {
login, value, _, err := db.MintRunToken(oltp.RunTokenSpec{Run: run, Repo: "vault", Job: "j" + letters, Letters: letters, Expires: time.Now().Add(time.Hour)})
if err != nil {
t.Fatal(err)
}
return login, value
}
writer, wv := mint("r1", "gio")
reader, rv := mint("r2", "go")
if code := g.callAPI("vault", writer, wv); code != http.StatusOK {
t.Errorf("FAIL: a contents: write run token at the API door: %d", code)
}
if code := g.callAPI("vault", reader, rv); code != http.StatusForbidden {
t.Errorf("FAIL: a contents: read run token on an op needing i: %d; want 403", code)
}
if code := g.callAPI("other", writer, wv); code == http.StatusOK {
t.Errorf("FAIL: a run token reached a repository it does not name")
}
if !g.rg.SyncReadGate(basicReq(reader, rv), "vault", "") {
t.Errorf("FAIL: a run token cannot clone its private repository over the sync lane's Basic round")
}
if g.rg.SyncReadGate(basicReq(reader, rv), "other", "") {
t.Errorf("FAIL: a run token clones a private repository it does not name")
}
if err := db.CompleteCIRun("r1", "failed", ""); err != nil {
t.Fatal(err)
}
if code := g.callAPI("vault", writer, wv); code != http.StatusUnauthorized {
t.Errorf("FAIL: a failed run's token at the API door: %d; want 401", code)
}
req := httptest.NewRequest(http.MethodPost, "/api/v1/r/vault/door-test", nil)
req.SetBasicAuth(writer, wv)
w := httptest.NewRecorder()
g.api.ServeHTTP(w, req)
if w.Code != http.StatusUnauthorized || !strings.Contains(w.Body.String(), "is over") {
t.Errorf("FAIL: a failed run's token was answered %d %q; want 401 saying the run is over", w.Code, w.Body)
}
if _, err := db.EndCIRun("r2", "cancelled", "timed out"); err != nil {
t.Fatal(err)
}
if g.rg.SyncReadGate(basicReq(reader, rv), "vault", "") {
t.Errorf("FAIL: an ended run's token still reads over sync")
}
if err := db.AddAccount("run-x", "pw-irrelevant-1", false); err == nil {
t.Errorf("FAIL: an account named run-x was created")
}
t.Log("ok: run-<id> at the API door and the sync lane, its repository only, its letters only, ended with its run")
}