1// SPDX-License-Identifier: AGPL-3.0-or-later
2
3package usecase
4
5import (
6 "context"
7 "testing"
8 "time"
9
10 "github.com/stretchr/testify/mock"
11 "github.com/stretchr/testify/require"
12
13 "bnema.dev/ermite/internal/domain"
14 "bnema.dev/ermite/internal/ports/mocks"
15)
16
17func TestAutoMergePendingIndex(t *testing.T) {
18 ctx := context.Background()
19 repo, err := domain.ParseRepoName("proj")
20 require.NoError(t, err)
21 store := mocks.NewMergeRequestStore(t)
22 meta := mocks.NewRepoMetaStore(t)
23 a := NewAutoMerge(ctx, AutoMergeConfig{Store: store, Meta: meta})
24 defer a.Close(ctx)
25 // No expectations: an unindexed kick must not read either store.
26 a.OnRepoChanged(ctx, repo)
27 _, found := a.next()
28 require.False(t, found)
29 a.OnAutoMergeEnabled(ctx, repo)
30 a.OnRepoChanged(ctx, repo)
31 got, found := a.next()
32 require.True(t, found)
33 require.Equal(t, repo, got)
34 meta.EXPECT().Get(mock.Anything, repo).Return(domain.Repo{Name: repo}, nil).Once()
35 store.EXPECT().ListOpen(mock.Anything, repo).Return(nil, nil).Once()
36 require.True(t, a.evaluate(repo))
37 require.NotContains(t, a.pending, repo)
38 a.mu.Lock()
39 delete(a.running, repo)
40 a.mu.Unlock()
41 a.OnRepoChanged(ctx, repo)
42 _, found = a.next()
43 require.False(t, found)
44}
45
46func TestAutoMergeEnableDuringEmptyScanKeepsPending(t *testing.T) {
47 ctx := context.Background()
48 repo, err := domain.ParseRepoName("proj")
49 require.NoError(t, err)
50 store := mocks.NewMergeRequestStore(t)
51 meta := mocks.NewRepoMetaStore(t)
52 a := NewAutoMerge(ctx, AutoMergeConfig{Store: store, Meta: meta})
53 defer a.Close(ctx)
54 a.OnAutoMergeEnabled(ctx, repo)
55 _, found := a.next()
56 require.True(t, found)
57 meta.EXPECT().Get(mock.Anything, repo).Return(domain.Repo{Name: repo}, nil).Once()
58 store.EXPECT().ListOpen(mock.Anything, repo).Run(func(context.Context, domain.RepoName) {
59 // The returned empty snapshot predates this newly enabled intent.
60 a.OnAutoMergeEnabled(ctx, repo)
61 }).Return(nil, nil).Once()
62 require.True(t, a.evaluate(repo))
63 require.True(t, a.pending[repo])
64 require.True(t, a.again[repo])
65}
66
67func TestAutoMergeStartupFillsPendingIndex(t *testing.T) {
68 ctx := context.Background()
69 repo, err := domain.ParseRepoName("proj")
70 require.NoError(t, err)
71 store := mocks.NewMergeRequestStore(t)
72 meta := mocks.NewRepoMetaStore(t)
73 store.EXPECT().ListOpenAll(mock.Anything).Return([]domain.MergeRequest{{Repo: repo, AutoMerge: &domain.AutoMerge{}}}, nil).Once()
74 scanned := make(chan struct{})
75 meta.EXPECT().Get(mock.Anything, repo).Run(func(context.Context, domain.RepoName) { close(scanned) }).Return(domain.Repo{}, domain.ErrNotFound).Once()
76 a := NewAutoMerge(ctx, AutoMergeConfig{Store: store, Meta: meta})
77 a.Start()
78 defer a.Close(ctx)
79 select {
80 case <-scanned:
81 case <-time.After(5 * time.Second):
82 t.Fatal("startup did not scan the pending repository")
83 }
84 a.mu.Lock()
85 pending := a.pending[repo]
86 a.mu.Unlock()
87 require.True(t, pending)
88}
89
90func TestManualPendingChecksMessage(t *testing.T) {
91 err := checksPolicy([]domain.CICheck{{Name: "build", Status: domain.CIQueued}})
92 require.ErrorIs(t, err, domain.ErrPreconditionFailed)
93 require.EqualError(t, err, "failed precondition: checks have not finished: build")
94 require.NotContains(t, err.Error(), "auto-merge")
95 require.NotErrorIs(t, err, errAutoMergeWait)
96 require.NoError(t, checksPolicy(nil))
97}