1package tinygit23import (4 "context"5 "path/filepath"6 "strings"7 "testing"89 "github.com/FelineStateMachine/tinyrelay/internal/event"10 "github.com/FelineStateMachine/tinyrelay/internal/storage"11)1213type maintainerFunc func(context.Context, Repository, string) bool1415func (f maintainerFunc) IsMaintainer(ctx context.Context, r Repository, pubkey string) bool {16 return f(ctx, r, pubkey)17}1819// The host's maintainer answer is consulted after the owner and the20// maintainers tag, and it decides whether a state event from another key is21// accepted.22func TestMaintainerSourceDecidesStateAuthority(t *testing.T) {23 root := t.TempDir()24 store, err := storage.Open(context.Background(), filepath.Join(root, "relay.db"))25 if err != nil {26 t.Fatal(err)27 }28 defer store.Close()29 vouched := map[string]bool{}30 var asked []string31 g, err := New(Config{Store: tinyStore(store), Root: filepath.Join(root, "git"), Maintainers: maintainerFunc(func(_ context.Context, r Repository, pubkey string) bool {32 asked = append(asked, r.Identifier+":"+pubkey)33 return vouched[pubkey]34 })})35 if err != nil {36 t.Fatal(err)37 }38 ownerSecret, agentSecret := strings.Repeat("0", 63)+"1", strings.Repeat("0", 63)+"2"39 owner, _ := event.PublicKey(ownerSecret)40 agent, _ := event.PublicKey(agentSecret)41 announcement := event.Event{Kind: 30617, CreatedAt: 100, Tags: [][]string{{"d", "demo"}, {"clone", "https://relay.example/" + owner + "/demo.git"}}}42 if err := event.Sign(&announcement, ownerSecret); err != nil {43 t.Fatal(err)44 }45 if err := g.Publish(context.Background(), announcement); err != nil {46 t.Fatal(err)47 }48 state := func(createdAt int64) event.Event {49 e := event.Event{Kind: 30618, CreatedAt: createdAt, Tags: [][]string{{"d", "demo"}, {"HEAD", "ref: refs/heads/main"}, {"refs/heads/main", strings.Repeat("a", 40)}}}50 if err := event.Sign(&e, agentSecret); err != nil {51 t.Fatal(err)52 }53 return e54 }55 if _, err := g.Validate(context.Background(), state(101)); err == nil || !strings.Contains(err.Error(), "not an accepted repository maintainer") {56 t.Fatalf("state from an unvouched key: %v", err)57 }58 if len(asked) == 0 || asked[0] != "demo:"+agent {59 t.Fatalf("source was not consulted for the repository: %v", asked)60 }61 vouched[agent] = true62 repo, err := g.Validate(context.Background(), state(102))63 if err != nil {64 t.Fatalf("state from a vouched key: %v", err)65 }66 if repo.Owner != owner || repo.Refs["refs/heads/main"] != strings.Repeat("a", 40) {67 t.Fatalf("state bound to the wrong repository: %+v", repo)68 }69 if !g.IsMaintainer(context.Background(), repo, owner) || g.IsMaintainer(context.Background(), repo, "") {70 t.Fatal("owner must count and an empty key must not")71 }72 vouched[agent] = false73 if _, err := g.Validate(context.Background(), state(103)); err == nil {74 t.Fatal("a withdrawn answer must reject the next state")75 }76}