v0.33.0 cf26365

mirrors_notify_test.go RawHistoryFolder

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}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 2

Tags 44

↑↓ moveenter openesc close