Skip to content
quarry Artifact [5d578395a7]
Login

Artifact [5d578395a7]

Artifact 5d578395a7a01cc744aa1f11e8b87a5d0cbd11c868850b86fb84392aa345e90e:


package runner

import (
	"archive/tar"
	"bytes"
	"compress/gzip"
	"context"
	"net/http"
	"net/http/httptest"
	"os"
	"path/filepath"
	"strings"
	"testing"
)

type entry struct {
	name, body, link string
	mode             int64
	dir              bool
}

func tarball(t *testing.T, entries ...entry) []byte {
	t.Helper()
	var buf bytes.Buffer
	gz := gzip.NewWriter(&buf)
	tw := tar.NewWriter(gz)
	for _, e := range entries {
		h := &tar.Header{Name: e.name, Mode: e.mode}
		switch {
		case e.dir:
			h.Typeflag = tar.TypeDir
		case e.link != "":
			h.Typeflag, h.Linkname = tar.TypeSymlink, e.link
		default:
			h.Typeflag, h.Size = tar.TypeReg, int64(len(e.body))
		}
		if h.Mode == 0 {
			h.Mode = 0o644
		}
		if err := tw.WriteHeader(h); err != nil {
			t.Fatal(err)
		}
		if h.Typeflag == tar.TypeReg {
			tw.Write([]byte(e.body))
		}
	}
	tw.Close()
	gz.Close()
	return buf.Bytes()
}

// serve answers GET /r/demo/archive/<checkin> with body, and remembers
// the Basic credentials it was sent.
func serve(t *testing.T, body []byte, gotUser *string) *httptest.Server {
	t.Helper()
	return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
		if !strings.HasPrefix(req.URL.Path, "/r/demo/archive/") {
			http.NotFound(w, req)
			return
		}
		if u, _, ok := req.BasicAuth(); ok && gotUser != nil {
			*gotUser = u
		}
		w.Write(body)
	}))
}

func TestAnArchiveCheckoutUnpacksTheTreeWithoutItsTopDirectory(t *testing.T) {
	srv := serve(t, tarball(t,
		entry{name: "demo-abc123/", dir: true},
		entry{name: "demo-abc123/ci/runner.json", body: `{"tasks":{"demo.test":["true"]}}`},
		entry{name: "demo-abc123/run.sh", body: "#!/bin/sh\n", mode: 0o755},
		entry{name: "demo-abc123/docs/link", link: "../ci/runner.json"},
	), nil)
	defer srv.Close()
	dir := filepath.Join(t.TempDir(), "ws")
	os.MkdirAll(dir, 0o755)
	os.WriteFile(filepath.Join(dir, "stale.txt"), []byte("from the last task"), 0o644)
	a := ArchiveCheckout{URL: srv.URL + "/r/demo"}
	if err := a.Checkout(context.Background(), dir, "abc123"); err != nil {
		t.Fatal(err)
	}
	if _, err := os.Stat(filepath.Join(dir, "ci", "runner.json")); err != nil {
		t.Errorf("the manifest is not at the tree's root: %v", err)
	}
	if _, err := os.Stat(filepath.Join(dir, "stale.txt")); err == nil {
		t.Error("a file from the previous check-in survived the checkout")
	}
	if st, err := os.Stat(filepath.Join(dir, "run.sh")); err != nil || st.Mode().Perm()&0o100 == 0 {
		t.Errorf("an executable arrived without its exec bit: %v %v", st, err)
	}
	if got, err := os.ReadFile(filepath.Join(dir, "docs", "link")); err != nil || !strings.Contains(string(got), "demo.test") {
		t.Errorf("a link inside the tree was not kept: %q %v", got, err)
	}
}

func TestAnArchiveThatReachesOutsideTheTreeIsRefused(t *testing.T) {
	for name, e := range map[string]entry{
		"dotdot":        {name: "demo-x/../../escape.txt", body: "x"},
		"absolute link": {name: "demo-x/l", link: "/etc/passwd"},
		"escaping link": {name: "demo-x/l", link: "../../outside"},
	} {
		t.Run(name, func(t *testing.T) {
			srv := serve(t, tarball(t, e), nil)
			defer srv.Close()
			root := t.TempDir()
			dir := filepath.Join(root, "ws")
			err := ArchiveCheckout{URL: srv.URL + "/r/demo"}.Checkout(context.Background(), dir, "x")
			if err == nil {
				t.Fatal("the checkout accepted it")
			}
			if _, err := os.Stat(filepath.Join(root, "escape.txt")); err == nil {
				t.Error("a file was written outside the workspace")
			}
			if _, err := os.Stat(dir); err == nil {
				t.Error("a refused archive left a workspace behind")
			}
		})
	}
}

func TestAPrivateRepositorysArchiveIsFetchedAsItsReader(t *testing.T) {
	var got string
	srv := serve(t, tarball(t, entry{name: "demo-x/a", body: "a"}), &got)
	defer srv.Close()
	a := ArchiveCheckout{URL: srv.URL + "/r/demo", User: "jan", Password: "sync-token"}
	if err := a.Checkout(context.Background(), filepath.Join(t.TempDir(), "ws"), "x"); err != nil {
		t.Fatal(err)
	}
	if got != "jan" {
		t.Errorf("the archive was fetched as %q, want jan", got)
	}
}

func TestAMissingArchiveFailsTheCheckoutWithTheForgesAnswer(t *testing.T) {
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
		http.Error(w, "no such repository", http.StatusNotFound)
	}))
	defer srv.Close()
	err := ArchiveCheckout{URL: srv.URL + "/r/demo"}.Checkout(context.Background(), filepath.Join(t.TempDir(), "ws"), "x")
	if err == nil || !strings.Contains(err.Error(), "404") {
		t.Fatalf("got %v", err)
	}
}