1package idle
2
3import (
4 "context"
5 "errors"
6 "testing"
7
8 "github.com/bnema/dumber/internal/infrastructure/idle/mocks"
9 "github.com/stretchr/testify/require"
10)
11
12const testHandle = "/org/freedesktop/portal/desktop/request/dumber/test"
13
14var errPortal = errors.New("portal error")
15
16// newTestInhibitor returns an inhibitor whose portal answered Version.
17func newTestInhibitor(t *testing.T) (*PortalInhibitor, *mocks.MockPortalClient) {
18 t.Helper()
19 portal := mocks.NewMockPortalClient(t)
20 portal.EXPECT().Version().Return(uint32(3), nil).Once()
21 return newPortalInhibitor(context.Background(), portal), portal
22}
23
24func TestNewPortalInhibitorClosesUnavailablePortal(t *testing.T) {
25 portal := mocks.NewMockPortalClient(t)
26 portal.EXPECT().Version().Return(uint32(0), errPortal).Once()
27 portal.EXPECT().Close().Return(nil).Once()
28
29 p := newPortalInhibitor(context.Background(), portal)
30 require.Nil(t, p.portal)
31
32 // Without a portal, the refcount still works and makes no portal call.
33 ctx := context.Background()
34 require.NoError(t, p.Inhibit(ctx, "video playing"))
35 require.True(t, p.IsInhibited())
36 require.NoError(t, p.Uninhibit(ctx))
37 require.False(t, p.IsInhibited())
38 require.NoError(t, p.Close())
39}
40
41func TestPortalInhibitorRefcountsOneRequest(t *testing.T) {
42 p, portal := newTestInhibitor(t)
43 ctx := context.Background()
44 portal.EXPECT().Inhibit("video playing").Return(testHandle, nil).Once()
45
46 require.NoError(t, p.Inhibit(ctx, "video playing"))
47 require.NoError(t, p.Inhibit(ctx, "second video"))
48 require.NoError(t, p.Uninhibit(ctx))
49 require.True(t, p.IsInhibited(), "released while one inhibit is still held")
50
51 portal.EXPECT().CloseRequest(testHandle).Return(nil).Once()
52 require.NoError(t, p.Uninhibit(ctx))
53 require.False(t, p.IsInhibited())
54
55 // Extra Uninhibit calls are no-ops.
56 require.NoError(t, p.Uninhibit(ctx))
57}
58
59func TestPortalInhibitorIgnoresCloseRequestError(t *testing.T) {
60 // A portal that already ended the request answers Close with an error.
61 p, portal := newTestInhibitor(t)
62 ctx := context.Background()
63 portal.EXPECT().Inhibit("video playing").Return(testHandle, nil).Once()
64 portal.EXPECT().CloseRequest(testHandle).Return(errPortal).Once()
65
66 require.NoError(t, p.Inhibit(ctx, "video playing"))
67 require.NoError(t, p.Uninhibit(ctx))
68
69 // The next inhibition starts a new request.
70 portal.EXPECT().Inhibit("next video").Return(testHandle, nil).Once()
71 require.NoError(t, p.Inhibit(ctx, "next video"))
72}
73
74func TestPortalInhibitorRollsBackFailedInhibit(t *testing.T) {
75 p, portal := newTestInhibitor(t)
76 ctx := context.Background()
77 portal.EXPECT().Inhibit("video playing").Return("", errPortal).Once()
78
79 require.ErrorIs(t, p.Inhibit(ctx, "video playing"), errPortal)
80 require.False(t, p.IsInhibited())
81
82 // Nothing to release: no CloseRequest expected.
83 require.NoError(t, p.Uninhibit(ctx))
84}
85
86func TestPortalInhibitorClose(t *testing.T) {
87 tests := []struct {
88 name string
89 inhibit bool
90 closeErr error
91 }{
92 {name: "releases active request", inhibit: true},
93 {name: "without active request"},
94 {name: "returns connection close error once", closeErr: errPortal},
95 }
96 for _, tt := range tests {
97 t.Run(tt.name, func(t *testing.T) {
98 p, portal := newTestInhibitor(t)
99 if tt.inhibit {
100 portal.EXPECT().Inhibit("video playing").Return(testHandle, nil).Once()
101 portal.EXPECT().CloseRequest(testHandle).Return(nil).Once()
102 require.NoError(t, p.Inhibit(context.Background(), "video playing"))
103 }
104 portal.EXPECT().Close().Return(tt.closeErr).Once()
105
106 require.ErrorIs(t, p.Close(), tt.closeErr)
107 require.False(t, p.IsInhibited())
108 // Close is idempotent.
109 require.NoError(t, p.Close())
110 })
111 }
112}