clone

a self-hosted multitenant nostr relay with sites, file storage, git hosting, and inbox and outbox delivery

feat/ui-updates
tinyrelay / internal / daemon / git_live_test.go
git_live_test.go 3.5K
1package daemon23import (4 "context"5 "strings"6 "testing"7 "time"89 "github.com/FelineStateMachine/tinyrelay/internal/event"10 gitrelay "github.com/FelineStateMachine/tinyrelay/tinygit"11)1213func TestGitLiveEnabledRequiresGRASP02(t *testing.T) {14 _, tenant := testTenant(t)15 p := tenant.Policy()16 p.Features.Grasp = true17 p.Features.Grasp02 = false18 if err := tenant.applyPolicy(context.Background(), p); err != nil {19 t.Fatal(err)20 }21 if tenant.gitLiveEnabled() {22 t.Fatal("live synchronization enabled without GRASP-02")23 }24 p.Features.Grasp02 = true25 if err := tenant.applyPolicy(context.Background(), p); err != nil {26 t.Fatal(err)27 }28 if !tenant.gitLiveEnabled() {29 t.Fatal("live synchronization disabled with GRASP-02")30 }31}3233func TestGitLivePrivateTargetRequiresConfiguredPeer(t *testing.T) {34 _, tenant := testTenant(t)35 repo := gitrelay.Repository{Private: true}36 if tenant.gitLiveAuthorized(repo, "wss://peer.example") {37 t.Fatal("unconfigured private target was authorized")38 }39 p := tenant.Policy()40 p.Features.Grasp, p.Features.Grasp02, p.Features.Grasp08 = true, true, true41 p.Reads = "members"42 p.PrivatePeers = []string{"wss://peer.example"}43 if err := tenant.applyPolicy(context.Background(), p); err != nil {44 t.Fatal(err)45 }46 if !tenant.gitLiveAuthorized(repo, "wss://peer.example") {47 t.Fatal("configured private target was rejected")48 }49 if tenant.gitLiveAuthorized(repo, "wss://other.example") {50 t.Fatal("different private target was authorized")51 }52}5354func TestGitLiveFiltersIncludeConversationRootsAndReplies(t *testing.T) {55 repo := gitrelay.Repository{Owner: strings.Repeat("a", 64), Identifier: "repo"}56 root := event.Event{Kind: event.KIND_GIT_ISSUE, ID: strings.Repeat("b", 64)}57 filters := gitLiveFiltersForRoots(repo, []event.Event{root})58 if len(filters) != 5 {59 t.Fatalf("got %d filters, want metadata, conversation and two reply filters", len(filters))60 }61 for _, key := range []string{"e", "E"} {62 found := false63 for _, filter := range filters {64 if len(filter.Tags[key]) == 1 && filter.Tags[key][0] == root.ID {65 found = true66 }67 }68 if !found {69 t.Fatalf("missing reply filter for %s", key)70 }71 }72}7374func TestGitLiveCohortRespectsSocketCapAndRotates(t *testing.T) {75 keys := make([]string, maxGitLivePeers+7)76 for i := range keys {77 keys[i] = strings.Repeat("a", i+1)78 }79 first := gitLiveCohort(keys, 0)80 second := gitLiveCohort(keys, maxGitLivePeers)81 if len(first) != maxGitLivePeers || len(second) != maxGitLivePeers {82 t.Fatalf("cohort sizes = %d, %d; want %d", len(first), len(second), maxGitLivePeers)83 }84 _, firstHas := first[keys[maxGitLivePeers-1]]85 _, secondHas := second[keys[maxGitLivePeers-1]]86 if firstHas == secondHas {87 t.Fatal("live cohort did not rotate")88 }89}9091func TestRotateGitAuthorsUsesDurableOffset(t *testing.T) {92 authors := []string{"a", "b", "c"}93 got := rotateGitAuthors(authors, 1)94 if strings.Join(got, ",") != "b,c,a" {95 t.Fatalf("rotated authors = %v", got)96 }97}9899func TestStopGitLiveWorkersCancelsAndWaits(t *testing.T) {100 done := make(chan struct{})101 canceled := make(chan struct{})102 workers := map[string]gitLiveWorker{"peer": {103 cancel: func() { close(canceled); close(done) },104 done: done,105 }}106 finished := make(chan struct{})107 go func() {108 stopGitLiveWorkers(workers)109 close(finished)110 }()111 select {112 case <-canceled:113 case <-time.After(time.Second):114 t.Fatal("worker was not canceled")115 }116 select {117 case <-finished:118 case <-time.After(time.Second):119 t.Fatal("stop returned before worker completion")120 }121 if len(workers) != 0 {122 t.Fatal("stopped workers remain in the registry")123 }124}