1// SPDX-License-Identifier: AGPL-3.0-or-later
2
3package usecase_test
4
5import (
6 "context"
7 "testing"
8 "testing/synctest"
9 "time"
10
11 "github.com/stretchr/testify/mock"
12 "github.com/stretchr/testify/require"
13
14 "bnema.dev/ermite/internal/domain"
15 "bnema.dev/ermite/internal/ports"
16 "bnema.dev/ermite/internal/ports/mocks"
17)
18
19func TestMirrorsPushRecordNotification(t *testing.T) {
20 f := newPushFixture(t)
21 n := name(t, "tools")
22 trigger := mocks.NewMirrorTrigger(t)
23 updates := []domain.RefUpdate{}
24 f.meta.On("Mutate", mock.Anything, n, mock.Anything).Return(domain.Repo{Name: n}, nil)
25 f.git.On("DefaultBranch", mock.Anything, n).Return("main", true, nil)
26 f.bus.On("Publish", mock.Anything, mock.Anything).Return()
27 f.sched.On("Schedule", mock.Anything, n).Return()
28 trigger.EXPECT().OnPush(mock.Anything, n, updates).Return().Once() // synchronous: verified when Record returns
29 require.NoError(t, f.pushes.WithMirror(trigger).Record(context.Background(), domain.Root(), n, updates))
30}
31
32func TestMirrorsMergeNotifiesMirror(t *testing.T) {
33 r := newMRRig(t)
34 r.seed()
35 trigger := mocks.NewMirrorTrigger(t)
36 notified := make(chan []domain.RefUpdate, 1)
37 trigger.EXPECT().OnPush(mock.Anything, r.repo, mock.Anything).Run(func(_ context.Context, _ domain.RepoName, u []domain.RefUpdate) { notified <- u }).Return().Once()
38 r.mrs.WithMirror(trigger)
39 head := r.feature("f0")
40 mr := r.open("f0")
41 _, err := r.mrs.Merge(r.ctx, root, r.repo, mr.Number, ports.MergeParams{HeadID: head})
42 require.NoError(t, err)
43 r.mrs.Wait(r.ctx)
44 select {
45 case u := <-notified:
46 require.Equal(t, "refs/heads/main", u[0].Ref)
47 case <-time.After(time.Second):
48 t.Fatal("merge did not notify mirrors")
49 }
50}
51
52func TestMirrorsDefaultBranchChangeNotifiesMirror(t *testing.T) {
53 f := newFixture(t)
54 ctx := context.Background()
55 n := name(t, "tools")
56 repo := domain.Repo{Name: n, Visibility: domain.VisibilityPrivate}
57 trigger := mocks.NewMirrorTrigger(t)
58 trigger.EXPECT().OnPush(mock.Anything, n, mock.Anything).Return().Once()
59 f.repos.WithMirror(trigger)
60 f.meta.EXPECT().Get(ctx, n).Return(repo, nil)
61 f.git.EXPECT().HasBranch(ctx, n, "dev").Return(true, nil)
62 f.git.EXPECT().SetDefaultBranch(ctx, n, "dev").Return(nil)
63 f.bus.EXPECT().Publish(ctx, mock.Anything).Return().Once()
64 _, err := f.repos.Update(ctx, tokenPrincipal(domain.ScopeRepoAdmin, "tools"), n, ports.UpdateRepoParams{DefaultBranch: ptr("dev")})
65 require.NoError(t, err)
66}
67
68func TestMirrorsExternalMutationMakesNextDueTickSync(t *testing.T) {
69 f := newMirrorFixture(t)
70 f.pushes(t, nil)
71 _, err := f.m.Sync(context.Background(), domain.Root(), f.repo, true)
72 require.NoError(t, err)
73 f.advance(5 * time.Minute)
74 f.m.ScheduleNow(context.Background())
75 f.drain(t)
76 require.Equal(t, 1, f.refReads(), "due and clean: skipped")
77 f.m.OnPush(context.Background(), f.repo, []domain.RefUpdate{{Ref: "refs/heads/main"}})
78 f.advance(5 * time.Minute)
79 f.m.ScheduleNow(context.Background())
80 f.drain(t)
81 require.Equal(t, 2, f.refReads(), "dirty repository synced on the next due tick")
82}
83
84func TestMirrorsEventsAreDeliveredByVisibility(t *testing.T) {
85 synctest.Test(t, func(t *testing.T) {
86 r := newEventRig(t, 0)
87 pub := r.repo("pub", domain.VisibilityPublic)
88 priv := r.repo("priv", domain.VisibilityPrivate)
89 ctx, cancel := context.WithCancel(context.Background())
90 defer cancel()
91 anon := r.watch(ctx, domain.Anonymous(), ports.WatchParams{})
92 root := r.watch(ctx, domain.Root(), ports.WatchParams{})
93 synctest.Wait()
94 for _, n := range []domain.RepoName{priv, pub} {
95 vis := domain.VisibilityPublic
96 if n == priv {
97 vis = domain.VisibilityPrivate
98 }
99 r.feed <- domain.MirrorFailed{Repo: n, State: domain.MirrorPaused, Class: domain.MirrorClassDiverged, At: now, Visibility: vis, CreatedAt: now}
100 r.feed <- domain.MirrorRecovered{Repo: n, At: now, Visibility: vis, CreatedAt: now}
101 }
102 for _, n := range []domain.RepoName{priv, pub} {
103 failed := root.next(t).Event.(domain.MirrorFailed)
104 require.Equal(t, n, failed.Repo)
105 require.Equal(t, n, root.next(t).Event.(domain.MirrorRecovered).Repo)
106 }
107 failed := anon.next(t).Event.(domain.MirrorFailed)
108 require.Equal(t, pub, failed.Repo, "a private repository's mirror events stay private")
109 require.Equal(t, pub, anon.next(t).Event.(domain.MirrorRecovered).Repo)
110 anon.quiet(t, r, pub)
111 })
112}