mesh/merge-gate head dc3a177410fa merged into trunk c0880c4dc271 — pass: builds gitea → novox; no bus step; every machine composes with the change as it di…
mesh/repo-check head dc3a177410fa merged into trunk c0880c4dc271 — pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-…
mesh/delivery delivered
296 lines
10 KiB
Go
296 lines
10 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"sort"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// sourceRepo is another repository of the fake forge, answering the contents, branch, repository and commit
|
|
// reads in the shapes the module already parses elsewhere (forge.go, reads.go, commit.go); a file's content
|
|
// is the forge's base64 "content" with its "encoding".
|
|
type sourceRepo struct {
|
|
branches map[string]string
|
|
trees map[string]map[string]string // commit -> path -> content
|
|
symlinks map[string]bool // paths that are symlinks, in every tree
|
|
moveTo map[string]string // branch -> commit it moves to right after it is first read
|
|
tags map[string]string
|
|
reads []string
|
|
}
|
|
|
|
func (f *fakeForge) addSource(name string, s *sourceRepo) {
|
|
if f.sources == nil {
|
|
f.sources = map[string]*sourceRepo{}
|
|
}
|
|
f.sources[name] = s
|
|
}
|
|
|
|
func (f *fakeForge) handleSource(w http.ResponseWriter, r *http.Request, path string) {
|
|
rest := strings.TrimPrefix(path, "/api/v1/repos/acme/")
|
|
name, rest, _ := strings.Cut(rest, "/")
|
|
s := f.sources[name]
|
|
if s == nil {
|
|
http.NotFound(w, r)
|
|
return
|
|
}
|
|
reply := func(v any) { _ = json.NewEncoder(w).Encode(v) }
|
|
s.reads = append(s.reads, r.Method+" "+path+"?"+r.URL.RawQuery)
|
|
switch {
|
|
case rest == "":
|
|
reply(map[string]any{"full_name": "acme/" + name, "default_branch": "main"})
|
|
case strings.HasPrefix(rest, "branches/"):
|
|
b := strings.TrimPrefix(rest, "branches/")
|
|
c, ok := s.branches[b]
|
|
if !ok {
|
|
http.NotFound(w, r)
|
|
return
|
|
}
|
|
reply(map[string]any{"name": b, "commit": map[string]any{"id": c}})
|
|
if to, ok := s.moveTo[b]; ok {
|
|
delete(s.moveTo, b)
|
|
s.branches[b] = to
|
|
}
|
|
case rest == "commits":
|
|
if c, ok := s.tags[r.URL.Query().Get("sha")]; ok {
|
|
reply([]any{map[string]any{"sha": c}})
|
|
return
|
|
}
|
|
reply([]any{})
|
|
case strings.HasPrefix(rest, "contents/"):
|
|
p := strings.TrimPrefix(rest, "contents/")
|
|
tree, ok := s.trees[r.URL.Query().Get("ref")]
|
|
if !ok {
|
|
http.NotFound(w, r)
|
|
return
|
|
}
|
|
if content, ok := tree[p]; ok {
|
|
typ := "file"
|
|
if s.symlinks[p] {
|
|
typ = "symlink"
|
|
}
|
|
reply(map[string]any{"type": typ, "path": p, "name": p[strings.LastIndex(p, "/")+1:], "sha": blob(content),
|
|
"size": len(content), "encoding": "base64", "content": base64.StdEncoding.EncodeToString([]byte(content))})
|
|
return
|
|
}
|
|
// A directory: its entries, each with its full path.
|
|
entries := map[string]map[string]any{}
|
|
for q, content := range tree {
|
|
if !strings.HasPrefix(q, p+"/") {
|
|
continue
|
|
}
|
|
sub := strings.TrimPrefix(q, p+"/")
|
|
first, more, nested := strings.Cut(sub, "/")
|
|
_ = more
|
|
if nested {
|
|
entries[first] = map[string]any{"name": first, "path": p + "/" + first, "type": "dir"}
|
|
continue
|
|
}
|
|
typ := "file"
|
|
if s.symlinks[q] {
|
|
typ = "symlink"
|
|
}
|
|
entries[first] = map[string]any{"name": first, "path": q, "type": typ, "size": len(content), "sha": blob(content)}
|
|
}
|
|
if len(entries) == 0 {
|
|
http.NotFound(w, r)
|
|
return
|
|
}
|
|
var names []string
|
|
for n := range entries {
|
|
names = append(names, n)
|
|
}
|
|
sort.Strings(names)
|
|
list := []any{}
|
|
for _, n := range names {
|
|
list = append(list, entries[n])
|
|
}
|
|
reply(list)
|
|
default:
|
|
http.NotFound(w, r)
|
|
}
|
|
}
|
|
|
|
func newSource() *sourceRepo {
|
|
return &sourceRepo{
|
|
branches: map[string]string{"main": "s0"},
|
|
tags: map[string]string{"v1": "s0"},
|
|
trees: map[string]map[string]string{
|
|
"s0": {"lib/a.go": "package a\n", "lib/deep/b.go": "package b\n", "lib/deep/er/c.txt": "c\n", "top.md": "top\n", "other/x": "x\n"},
|
|
},
|
|
}
|
|
}
|
|
|
|
func copyAsk(t *testing.T, files ...map[string]any) CommitAsk {
|
|
return ask(t, map[string]any{"branch": "fix/1365-x", "files": filesArg(files...)})
|
|
}
|
|
|
|
func cp(path, repo, ref, src string) map[string]any {
|
|
return map[string]any{"path": path, "copy_from": map[string]any{"owner": "acme", "repo": repo, "ref": ref, "path": src}}
|
|
}
|
|
|
|
// Seen failing first: with the expansion switched off the commit carried no copy and the answer named no source.
|
|
func TestACopyFromAFileCommitsTheSourcesContentAndNamesItsCommit(t *testing.T) {
|
|
f := newFake(t)
|
|
f.addSource("src", newSource())
|
|
got, err := commit(context.Background(), f.serve(), copyAsk(t, cp("vendor/top.md", "src", "main", "top.md")))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if f.trees[got.Commit]["vendor/top.md"] != "top\n" {
|
|
t.Fatalf("tree %v", f.trees[got.Commit])
|
|
}
|
|
if len(got.Copies) != 1 || got.Copies[0].Source != "s0" || got.Copies[0].Path != "vendor/top.md" || got.Copies[0].Files != 1 {
|
|
t.Fatalf("copies %+v", got.Copies)
|
|
}
|
|
}
|
|
|
|
// Seen failing first, as above.
|
|
func TestACopyFromADirectoryCopiesEveryFileUnderItToTheSameRelativePaths(t *testing.T) {
|
|
f := newFake(t)
|
|
f.addSource("src", newSource())
|
|
got, err := commit(context.Background(), f.serve(), copyAsk(t, cp("pkg/lib", "src", "v1", "lib"),
|
|
map[string]any{"path": "own.md", "content": "own\n"}))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
tree := f.trees[got.Commit]
|
|
want := map[string]string{"pkg/lib/a.go": "package a\n", "pkg/lib/deep/b.go": "package b\n", "pkg/lib/deep/er/c.txt": "c\n", "own.md": "own\n"}
|
|
for p, c := range want {
|
|
if tree[p] != c {
|
|
t.Errorf("%s is %q, want %q", p, tree[p], c)
|
|
}
|
|
}
|
|
if tree["pkg/lib/top.md"] != "" || tree["pkg/other/x"] != "" {
|
|
t.Errorf("files outside the directory were copied: %v", tree)
|
|
}
|
|
if len(got.Copies) != 1 || got.Copies[0].Source != "s0" || got.Copies[0].Files != 3 || got.Copies[0].Bytes != len("package a\npackage b\nc\n") {
|
|
t.Fatalf("copies %+v", got.Copies)
|
|
}
|
|
if len(got.Files) != 4 {
|
|
t.Errorf("files %+v", got.Files)
|
|
}
|
|
}
|
|
|
|
// Seen failing first, as above: the files were read at the branch's new head.
|
|
func TestEveryCopiedFileIsReadAtTheOneShaTheRefResolvedTo(t *testing.T) {
|
|
f := newFake(t)
|
|
s := newSource()
|
|
s.trees["s1"] = map[string]string{"lib/a.go": "package MOVED\n", "lib/deep/b.go": "package MOVED\n", "lib/deep/er/c.txt": "MOVED\n"}
|
|
s.moveTo = map[string]string{"main": "s1"}
|
|
f.addSource("src", s)
|
|
got, err := commit(context.Background(), f.serve(), copyAsk(t, cp("pkg", "src", "main", "lib")))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if f.trees[got.Commit]["pkg/a.go"] != "package a\n" || got.Copies[0].Source != "s0" {
|
|
t.Fatalf("tree %v, copies %+v", f.trees[got.Commit], got.Copies)
|
|
}
|
|
for _, r := range s.reads {
|
|
if strings.Contains(r, "/contents/") && !strings.Contains(r, "ref=s0") {
|
|
t.Errorf("a file was read at another ref: %s", r)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Seen failing first: each case was taken or refused for another reason (or sent).
|
|
func TestEveryRefusalOfACopySendsNothingAndSaysWhy(t *testing.T) {
|
|
big := strings.Repeat("z", maxFileBytes+1)
|
|
many := map[string]string{}
|
|
for i := 0; i < maxFiles+1; i++ {
|
|
many[fmt.Sprintf("many/f%03d", i)] = "m"
|
|
}
|
|
for name, c := range map[string]struct {
|
|
file map[string]any
|
|
want string
|
|
}{
|
|
"unknown repository": {cp("t", "nope", "main", "top.md"), "no repository acme/nope"},
|
|
"unknown ref": {cp("t", "src", "nothing", "top.md"), `no branch, tag or commit "nothing"`},
|
|
"unknown path": {cp("t", "src", "main", "missing/dir"), `no "missing/dir"`},
|
|
"symlink in source": {cp("t", "src", "main", "link"), "link is a symlink"},
|
|
"symlink under dir": {cp("t", "src", "main", "withlink"), "withlink/l is a symlink"},
|
|
"file over 4 MiB": {cp("t", "src", "main", "big"), "more than the 4.0 MiB"},
|
|
"over 200 files": {cp("t", "src", "main", "many"), "201 files"},
|
|
"with content": {map[string]any{"path": "t", "content": "x", "copy_from": cp("t", "src", "main", "top.md")["copy_from"]}, "copy_from together with content"},
|
|
"with base64": {map[string]any{"path": "t", "content_base64": "eA==", "copy_from": cp("t", "src", "main", "top.md")["copy_from"]}, "copy_from together with content"},
|
|
"with delete": {map[string]any{"path": "t", "delete": true, "copy_from": cp("t", "src", "main", "top.md")["copy_from"]}, "copy_from together with content"},
|
|
"meets a given file": {cp("pkg", "src", "main", "lib"), "pkg/a.go is named twice"},
|
|
} {
|
|
f := newFake(t)
|
|
s := newSource()
|
|
s.trees["s0"]["link"] = "top.md"
|
|
s.symlinks = map[string]bool{"link": true, "withlink/l": true}
|
|
s.trees["s0"]["withlink/l"] = "top.md"
|
|
s.trees["s0"]["withlink/ok"] = "ok"
|
|
s.trees["s0"]["big/f"] = big
|
|
for p, v := range many {
|
|
s.trees["s0"][p] = v
|
|
}
|
|
f.addSource("src", s)
|
|
files := []map[string]any{c.file}
|
|
if name == "meets a given file" {
|
|
files = append(files, map[string]any{"path": "pkg/a.go", "content": "mine"})
|
|
}
|
|
a, err := readCommitAsk(map[string]any{"owner": "acme", "repo": "app", "message": "m", "branch": "fix/1365-x", "files": filesArg(files...)})
|
|
if err == nil {
|
|
_, err = commit(context.Background(), f.serve(), a)
|
|
}
|
|
if err == nil || !strings.Contains(err.Error(), c.want) || !strings.Contains(err.Error(), "Nothing was sent") {
|
|
t.Errorf("%s: %v (want %q)", name, err, c.want)
|
|
}
|
|
if len(f.writes) != 0 || len(f.patches) != 0 || f.branches["fix/1365-x"] != "" {
|
|
t.Errorf("%s: something was sent: %v %v", name, f.writes, f.branches)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Seen failing first: the copy was not counted, so the commit was sent.
|
|
func TestACopyOverTheTotalNamesItsCountAndSizeSoItCanBeSplit(t *testing.T) {
|
|
f := newFake(t)
|
|
s := newSource()
|
|
chunk := strings.Repeat("q", maxFileBytes)
|
|
for i := 0; i < 5; i++ {
|
|
s.trees["s0"][fmt.Sprintf("heavy/f%d", i)] = chunk
|
|
}
|
|
f.addSource("src", s)
|
|
_, err := commit(context.Background(), f.serve(), copyAsk(t, cp("h", "src", "main", "heavy")))
|
|
if err == nil || !strings.Contains(err.Error(), "5 files, 20.0 MiB together") || !strings.Contains(err.Error(), "by subdirectory") {
|
|
t.Fatalf("%v", err)
|
|
}
|
|
if len(f.writes) != 0 {
|
|
t.Fatal("sent")
|
|
}
|
|
}
|
|
|
|
func TestCopyFromIsInTheToolsSchemaAndTheReadme(t *testing.T) {
|
|
var schema map[string]any
|
|
for _, tl := range Tools(func() (*Forge, error) { return nil, fmt.Errorf("unset") }) {
|
|
if tl.Name == "gitea_commit" {
|
|
schema = tl.Input
|
|
}
|
|
}
|
|
item := schema["properties"].(map[string]any)["files"].(map[string]any)["items"].(map[string]any)["properties"].(map[string]any)
|
|
cf, ok := item["copy_from"].(map[string]any)
|
|
if !ok {
|
|
t.Fatalf("copy_from is not in the schema: %v", item)
|
|
}
|
|
props := cf["properties"].(map[string]any)
|
|
for _, k := range []string{"owner", "repo", "ref", "path"} {
|
|
if _, ok := props[k]; !ok {
|
|
t.Errorf("copy_from has no %s", k)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestACopyFromThatIsNotAnObjectIsRefused(t *testing.T) {
|
|
_, err := readCommitAsk(map[string]any{"owner": "acme", "repo": "app", "message": "m", "branch": "b",
|
|
"files": filesArg(map[string]any{"path": "x", "copy_from": "acme/src:top.md"})})
|
|
if err == nil || !strings.Contains(err.Error(), "copy_from is an object") {
|
|
t.Fatalf("%v", err)
|
|
}
|
|
}
|