Live video on the AT Protocol
1package moderation
2
3import (
4 "context"
5 "fmt"
6 "testing"
7 "time"
8
9 "github.com/bluesky-social/indigo/api/bsky"
10 lexutil "github.com/bluesky-social/indigo/lex/util"
11 "github.com/stretchr/testify/require"
12 "stream.place/streamplace/pkg/streamplace"
13)
14
15func TestPermissionChecker_CheckPermission_StreamerSelfModeration(t *testing.T) {
16 mod := newMockModel()
17 pc := NewPermissionChecker(mod)
18
19 streamerDID := "did:plc:streamer123"
20
21 err := pc.CheckPermission(context.Background(), streamerDID, streamerDID, "createBlock")
22 require.NoError(t, err, "streamer should have permission for self-moderation")
23
24 err = pc.CheckPermission(context.Background(), streamerDID, streamerDID, "createGate")
25 require.NoError(t, err, "streamer should have permission for self-moderation")
26
27 err = pc.CheckPermission(context.Background(), streamerDID, streamerDID, "updateLivestream")
28 require.NoError(t, err, "streamer should have permission for self-moderation")
29}
30
31func TestPermissionChecker_CheckPermission_WithCorrectPermission(t *testing.T) {
32 mod := newMockModel()
33 pc := NewPermissionChecker(mod)
34
35 ctx := context.Background()
36 streamerDID := "did:plc:streamer123"
37 moderatorDID := "did:plc:moderator456"
38
39 mod.addPermissionView(streamerDID, moderatorDID, []string{"ban", "hide"}, nil)
40
41 err := pc.CheckPermission(ctx, moderatorDID, streamerDID, "createBlock")
42 require.NoError(t, err, "moderator with 'ban' permission should be able to createBlock")
43
44 err = pc.CheckPermission(ctx, moderatorDID, streamerDID, "createGate")
45 require.NoError(t, err, "moderator with 'hide' permission should be able to createGate")
46}
47
48func TestPermissionChecker_CheckPermission_WithWrongPermission(t *testing.T) {
49 mod := newMockModel()
50 pc := NewPermissionChecker(mod)
51
52 ctx := context.Background()
53 streamerDID := "did:plc:streamer123"
54 moderatorDID := "did:plc:moderator456"
55
56 mod.addPermissionView(streamerDID, moderatorDID, []string{"hide"}, nil)
57
58 err := pc.CheckPermission(ctx, moderatorDID, streamerDID, "createBlock")
59 require.Error(t, err, "moderator with only 'hide' permission should not be able to createBlock")
60 require.Contains(t, err.Error(), "does not have permission 'ban'")
61}
62
63func TestPermissionChecker_CheckPermission_WithoutAnyPermission(t *testing.T) {
64 mod := newMockModel()
65 pc := NewPermissionChecker(mod)
66
67 ctx := context.Background()
68 streamerDID := "did:plc:streamer123"
69 moderatorDID := "did:plc:moderator456"
70
71 err := pc.CheckPermission(ctx, moderatorDID, streamerDID, "createBlock")
72 require.Error(t, err, "moderator without any delegation should be denied")
73 require.Contains(t, err.Error(), "does not have permission")
74}
75
76func TestPermissionChecker_CheckPermission_UnknownAction(t *testing.T) {
77 mod := newMockModel()
78 pc := NewPermissionChecker(mod)
79
80 streamerDID := "did:plc:streamer123"
81
82 err := pc.CheckPermission(context.Background(), streamerDID, streamerDID, "unknownAction")
83 require.Error(t, err)
84 require.Contains(t, err.Error(), "unknown action")
85}
86
87func TestPermissionChecker_HasPermission(t *testing.T) {
88 mod := newMockModel()
89 pc := NewPermissionChecker(mod)
90
91 ctx := context.Background()
92 streamerDID := "did:plc:streamer123"
93 moderatorDID := "did:plc:moderator456"
94
95 mod.addPermissionView(streamerDID, moderatorDID, []string{"ban", "hide"}, nil)
96
97 has, err := pc.HasPermission(ctx, moderatorDID, streamerDID, PermissionBan)
98 require.NoError(t, err)
99 require.True(t, has, "should have 'ban' permission")
100
101 has, err = pc.HasPermission(ctx, moderatorDID, streamerDID, PermissionHide)
102 require.NoError(t, err)
103 require.True(t, has, "should have 'hide' permission")
104
105 has, err = pc.HasPermission(ctx, moderatorDID, streamerDID, PermissionLivestreamManage)
106 require.NoError(t, err)
107 require.False(t, has, "should not have 'livestream.manage' permission")
108}
109
110func TestActionPermissions_Mapping(t *testing.T) {
111 require.Equal(t, PermissionBan, ActionPermissions["createBlock"])
112 require.Equal(t, PermissionBan, ActionPermissions["deleteBlock"])
113 require.Equal(t, PermissionHide, ActionPermissions["createGate"])
114 require.Equal(t, PermissionHide, ActionPermissions["deleteGate"])
115 require.Equal(t, PermissionLivestreamManage, ActionPermissions["updateLivestream"])
116}
117
118func TestPermissionChecker_HasPermission_MultipleSeparateRecords(t *testing.T) {
119 mod := newMockModel()
120 pc := NewPermissionChecker(mod)
121
122 ctx := context.Background()
123 streamerDID := "did:plc:streamer123"
124 moderatorDID := "did:plc:moderator456"
125
126 mod.addPermissionView(streamerDID, moderatorDID, []string{"ban"}, nil)
127 mod.addPermissionView(streamerDID, moderatorDID, []string{"hide"}, nil)
128
129 hasBan, err := pc.HasPermission(ctx, moderatorDID, streamerDID, PermissionBan)
130 require.NoError(t, err)
131 require.True(t, hasBan, "should have 'ban' permission from first record")
132
133 hasHide, err := pc.HasPermission(ctx, moderatorDID, streamerDID, PermissionHide)
134 require.NoError(t, err)
135 require.True(t, hasHide, "should have 'hide' permission from second record")
136
137 err = pc.CheckPermission(ctx, moderatorDID, streamerDID, "createBlock")
138 require.NoError(t, err, "should allow createBlock with 'ban' permission from separate record")
139
140 err = pc.CheckPermission(ctx, moderatorDID, streamerDID, "createGate")
141 require.NoError(t, err, "should allow createGate with 'hide' permission from separate record")
142}
143
144func TestPermissionChecker_HasPermission_ExpiredDelegation(t *testing.T) {
145 mod := newMockModel()
146 pc := NewPermissionChecker(mod)
147
148 ctx := context.Background()
149 streamerDID := "did:plc:streamer123"
150 moderatorDID := "did:plc:moderator456"
151
152 expiredTime := time.Now().Add(-1 * time.Hour)
153 mod.addPermissionView(streamerDID, moderatorDID, []string{"ban"}, &expiredTime)
154
155 hasPermission, err := pc.HasPermission(ctx, moderatorDID, streamerDID, PermissionBan)
156 require.NoError(t, err)
157 require.False(t, hasPermission, "should deny permission for expired delegation")
158
159 err = pc.CheckPermission(ctx, moderatorDID, streamerDID, "createBlock")
160 require.Error(t, err, "should deny action for expired delegation")
161 require.Contains(t, err.Error(), "does not have permission")
162}
163
164func TestPermissionChecker_HasPermission_NotYetExpired(t *testing.T) {
165 mod := newMockModel()
166 pc := NewPermissionChecker(mod)
167
168 ctx := context.Background()
169 streamerDID := "did:plc:streamer123"
170 moderatorDID := "did:plc:moderator456"
171
172 futureTime := time.Now().Add(1 * time.Hour)
173 mod.addPermissionView(streamerDID, moderatorDID, []string{"ban", "hide"}, &futureTime)
174
175 hasPermission, err := pc.HasPermission(ctx, moderatorDID, streamerDID, PermissionBan)
176 require.NoError(t, err)
177 require.True(t, hasPermission, "should allow permission for not-yet-expired delegation")
178
179 err = pc.CheckPermission(ctx, moderatorDID, streamerDID, "createBlock")
180 require.NoError(t, err, "should allow action for not-yet-expired delegation")
181}
182
183func TestPermissionChecker_HasPermission_NoExpiration(t *testing.T) {
184 mod := newMockModel()
185 pc := NewPermissionChecker(mod)
186
187 ctx := context.Background()
188 streamerDID := "did:plc:streamer123"
189 moderatorDID := "did:plc:moderator456"
190
191 mod.addPermissionView(streamerDID, moderatorDID, []string{"ban", "hide"}, nil)
192
193 hasPermission, err := pc.HasPermission(ctx, moderatorDID, streamerDID, PermissionBan)
194 require.NoError(t, err)
195 require.True(t, hasPermission, "should allow permission for delegation with no expiration")
196
197 err = pc.CheckPermission(ctx, moderatorDID, streamerDID, "createBlock")
198 require.NoError(t, err, "should allow action for delegation with no expiration")
199}
200
201type mockModel struct {
202 delegations map[string][]*streamplace.ModerationDefs_PermissionView
203}
204
205func newMockModel() *mockModel {
206 return &mockModel{
207 delegations: make(map[string][]*streamplace.ModerationDefs_PermissionView),
208 }
209}
210
211func (m *mockModel) addPermissionView(streamerDID, moderatorDID string, permissions []string, expirationTime *time.Time) {
212 key := streamerDID + "_" + moderatorDID
213
214 var expTimeStr *string
215 if expirationTime != nil {
216 str := expirationTime.Format(time.RFC3339)
217 expTimeStr = &str
218 }
219
220 permRecord := &streamplace.ModerationPermission{
221 Moderator: moderatorDID,
222 Permissions: permissions,
223 ExpirationTime: expTimeStr,
224 }
225
226 view := &streamplace.ModerationDefs_PermissionView{
227 Uri: fmt.Sprintf("at://%s/place.stream.moderation.permission/test", streamerDID),
228 Cid: "bafytest",
229 Author: &bsky.ActorDefs_ProfileViewBasic{Did: streamerDID},
230 Record: &lexutil.LexiconTypeDecoder{Val: permRecord},
231 }
232
233 m.delegations[key] = append(m.delegations[key], view)
234}
235
236func (m *mockModel) GetModerationDelegations(ctx context.Context, streamerDID, moderatorDID string) ([]*streamplace.ModerationDefs_PermissionView, error) {
237 key := streamerDID + "_" + moderatorDID
238 delegations, exists := m.delegations[key]
239 if !exists {
240 return nil, nil
241 }
242 return delegations, nil
243}