Files
livekit/pkg/rtc/uptrackmanager_test.go
zeekay e9292da479 refactor: own the module path, and move the KV client to hanzokv/go
This fork lived at github.com/hanzoai/livekit but still declared
`module github.com/livekit/livekit-server`, so all 505 self-references pointed
at upstream. A fork that does not own its module path is the exact thing the
owned-module-paths rule exists to prevent: every import line in every consumer
carries someone else's brand, and `go get` resolves to their code, not ours.

  module + 505 self-imports  github.com/livekit/livekit-server -> github.com/hanzoai/livekit
  KV client                  github.com/redis/go-redis/v9      -> github.com/hanzokv/go/v9
  message bus                github.com/livekit/psrpc          -> github.com/hanzoai/psrpc

psrpc had to be forked (hanzoai/psrpc, Apache-2.0) because its bus is typed
against the KV client — the same shape redsync had for hanzoai/git.

DELIBERATELY NOT FORKED: github.com/livekit/{protocol,mediatransportutil,
mageutil} keep their addresses. Only `protocol` still reaches go-redis, through
a single file (redis/redis.go) out of 305. Forking an 11.5MB core wire-protocol
library that this server must track in lockstep — and that upstream pushed
yesterday — to change one file trades a tidy dependency graph for permanent
divergence on the fastest-moving dependency here. So one indirect
github.com/redis/go-redis entry remains, via livekit/protocol/redis, and that
is the correct place for it to live.
2026-07-26 08:14:23 -07:00

330 lines
11 KiB
Go

// Copyright 2023 LiveKit, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package rtc
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/livekit/protocol/livekit"
"github.com/livekit/protocol/logger"
"github.com/livekit/protocol/utils"
"github.com/hanzoai/livekit/pkg/rtc/types"
"github.com/hanzoai/livekit/pkg/rtc/types/typesfakes"
)
var defaultUptrackManagerParams = UpTrackManagerParams{
Logger: logger.GetLogger(),
VersionGenerator: utils.NewDefaultTimedVersionGenerator(),
}
func TestUpdateSubscriptionPermission(t *testing.T) {
t.Run("updates subscription permission", func(t *testing.T) {
um := NewUpTrackManager(defaultUptrackManagerParams)
vg := utils.NewDefaultTimedVersionGenerator()
tra := &typesfakes.FakeMediaTrack{}
tra.IDReturns("audio")
um.publishedTracks["audio"] = tra
trv := &typesfakes.FakeMediaTrack{}
trv.IDReturns("video")
um.publishedTracks["video"] = trv
// no restrictive subscription permission
subscriptionPermission := &livekit.SubscriptionPermission{
AllParticipants: true,
}
um.UpdateSubscriptionPermission(subscriptionPermission, vg.Next(), nil)
require.Nil(t, um.subscriberPermissions)
// nobody is allowed to subscribe
subscriptionPermission = &livekit.SubscriptionPermission{
TrackPermissions: []*livekit.TrackPermission{},
}
um.UpdateSubscriptionPermission(subscriptionPermission, vg.Next(), nil)
require.NotNil(t, um.subscriberPermissions)
require.Equal(t, 0, len(um.subscriberPermissions))
lp1 := &typesfakes.FakeLocalParticipant{}
lp1.IdentityReturns("p1")
lp2 := &typesfakes.FakeLocalParticipant{}
lp2.IdentityReturns("p2")
sidResolver := func(sid livekit.ParticipantID) types.LocalParticipant {
if sid == "p1" {
return lp1
}
if sid == "p2" {
return lp2
}
return nil
}
// allow all tracks for participants
perms1 := &livekit.TrackPermission{
ParticipantSid: "p1",
AllTracks: true,
}
perms2 := &livekit.TrackPermission{
ParticipantSid: "p2",
AllTracks: true,
}
subscriptionPermission = &livekit.SubscriptionPermission{
TrackPermissions: []*livekit.TrackPermission{
perms1,
perms2,
},
}
um.UpdateSubscriptionPermission(subscriptionPermission, vg.Next(), sidResolver)
require.Equal(t, 2, len(um.subscriberPermissions))
require.EqualValues(t, perms1, um.subscriberPermissions["p1"])
require.EqualValues(t, perms2, um.subscriberPermissions["p2"])
// allow all tracks for some and restrictive for others
perms1 = &livekit.TrackPermission{
ParticipantIdentity: "p1",
AllTracks: true,
}
perms2 = &livekit.TrackPermission{
ParticipantIdentity: "p2",
TrackSids: []string{"audio"},
}
perms3 := &livekit.TrackPermission{
ParticipantIdentity: "p3",
TrackSids: []string{"video"},
}
subscriptionPermission = &livekit.SubscriptionPermission{
TrackPermissions: []*livekit.TrackPermission{
perms1,
perms2,
perms3,
},
}
um.UpdateSubscriptionPermission(subscriptionPermission, vg.Next(), nil)
require.Equal(t, 3, len(um.subscriberPermissions))
require.EqualValues(t, perms1, um.subscriberPermissions["p1"])
require.EqualValues(t, perms2, um.subscriberPermissions["p2"])
require.EqualValues(t, perms3, um.subscriberPermissions["p3"])
})
t.Run("updates subscription permission using both", func(t *testing.T) {
um := NewUpTrackManager(defaultUptrackManagerParams)
vg := utils.NewDefaultTimedVersionGenerator()
tra := &typesfakes.FakeMediaTrack{}
tra.IDReturns("audio")
um.publishedTracks["audio"] = tra
trv := &typesfakes.FakeMediaTrack{}
trv.IDReturns("video")
um.publishedTracks["video"] = trv
lp1 := &typesfakes.FakeLocalParticipant{}
lp1.IdentityReturns("p1")
lp2 := &typesfakes.FakeLocalParticipant{}
lp2.IdentityReturns("p2")
sidResolver := func(sid livekit.ParticipantID) types.LocalParticipant {
if sid == "p1" {
return lp1
}
if sid == "p2" {
return lp2
}
return nil
}
// allow all tracks for participants
perms1 := &livekit.TrackPermission{
ParticipantSid: "p1",
ParticipantIdentity: "p1",
AllTracks: true,
}
perms2 := &livekit.TrackPermission{
ParticipantSid: "p2",
ParticipantIdentity: "p2",
AllTracks: true,
}
subscriptionPermission := &livekit.SubscriptionPermission{
TrackPermissions: []*livekit.TrackPermission{
perms1,
perms2,
},
}
err := um.UpdateSubscriptionPermission(subscriptionPermission, vg.Next(), sidResolver)
require.NoError(t, err)
require.Equal(t, 2, len(um.subscriberPermissions))
require.EqualValues(t, perms1, um.subscriberPermissions["p1"])
require.EqualValues(t, perms2, um.subscriberPermissions["p2"])
// mismatched identities should fail a permission update
badSidResolver := func(sid livekit.ParticipantID) types.LocalParticipant {
if sid == "p1" {
return lp2
}
if sid == "p2" {
return lp1
}
return nil
}
err = um.UpdateSubscriptionPermission(subscriptionPermission, vg.Next(), badSidResolver)
require.NoError(t, err)
require.Equal(t, 2, len(um.subscriberPermissions))
require.EqualValues(t, perms1, um.subscriberPermissions["p1"])
require.EqualValues(t, perms2, um.subscriberPermissions["p2"])
})
t.Run("update versions", func(t *testing.T) {
um := NewUpTrackManager(defaultUptrackManagerParams)
vg := utils.NewDefaultTimedVersionGenerator()
v0, v1, v2 := vg.Next(), vg.Next(), vg.Next()
um.UpdateSubscriptionPermission(&livekit.SubscriptionPermission{}, v1, nil)
require.Equal(t, v1.Load(), um.subscriptionPermissionVersion.Load(), "first update should be applied")
um.UpdateSubscriptionPermission(&livekit.SubscriptionPermission{}, v2, nil)
require.Equal(t, v2.Load(), um.subscriptionPermissionVersion.Load(), "ordered updates should be applied")
um.UpdateSubscriptionPermission(&livekit.SubscriptionPermission{}, v0, nil)
require.Equal(t, v2.Load(), um.subscriptionPermissionVersion.Load(), "out of order updates should be ignored")
um.UpdateSubscriptionPermission(&livekit.SubscriptionPermission{}, utils.TimedVersion(0), nil)
require.True(t, um.subscriptionPermissionVersion.After(v2), "zero version in updates should use next local version")
})
}
func TestSubscriptionPermission(t *testing.T) {
t.Run("checks subscription permission", func(t *testing.T) {
um := NewUpTrackManager(defaultUptrackManagerParams)
vg := utils.NewDefaultTimedVersionGenerator()
tra := &typesfakes.FakeMediaTrack{}
tra.IDReturns("audio")
um.publishedTracks["audio"] = tra
trv := &typesfakes.FakeMediaTrack{}
trv.IDReturns("video")
um.publishedTracks["video"] = trv
// no restrictive permission
subscriptionPermission := &livekit.SubscriptionPermission{
AllParticipants: true,
}
um.UpdateSubscriptionPermission(subscriptionPermission, vg.Next(), nil)
require.True(t, um.hasPermissionLocked("audio", "p1"))
require.True(t, um.hasPermissionLocked("audio", "p2"))
// nobody is allowed to subscribe
subscriptionPermission = &livekit.SubscriptionPermission{
TrackPermissions: []*livekit.TrackPermission{},
}
um.UpdateSubscriptionPermission(subscriptionPermission, vg.Next(), nil)
require.False(t, um.hasPermissionLocked("audio", "p1"))
require.False(t, um.hasPermissionLocked("audio", "p2"))
// allow all tracks for participants
subscriptionPermission = &livekit.SubscriptionPermission{
TrackPermissions: []*livekit.TrackPermission{
{
ParticipantIdentity: "p1",
AllTracks: true,
},
{
ParticipantIdentity: "p2",
AllTracks: true,
},
},
}
um.UpdateSubscriptionPermission(subscriptionPermission, vg.Next(), nil)
require.True(t, um.hasPermissionLocked("audio", "p1"))
require.True(t, um.hasPermissionLocked("video", "p1"))
require.True(t, um.hasPermissionLocked("audio", "p2"))
require.True(t, um.hasPermissionLocked("video", "p2"))
// add a new track after permissions are set
trs := &typesfakes.FakeMediaTrack{}
trs.IDReturns("screen")
um.publishedTracks["screen"] = trs
require.True(t, um.hasPermissionLocked("audio", "p1"))
require.True(t, um.hasPermissionLocked("video", "p1"))
require.True(t, um.hasPermissionLocked("screen", "p1"))
require.True(t, um.hasPermissionLocked("audio", "p2"))
require.True(t, um.hasPermissionLocked("video", "p2"))
require.True(t, um.hasPermissionLocked("screen", "p2"))
// allow all tracks for some and restrictive for others
subscriptionPermission = &livekit.SubscriptionPermission{
TrackPermissions: []*livekit.TrackPermission{
{
ParticipantIdentity: "p1",
AllTracks: true,
},
{
ParticipantIdentity: "p2",
TrackSids: []string{"audio"},
},
{
ParticipantIdentity: "p3",
TrackSids: []string{"video"},
},
},
}
um.UpdateSubscriptionPermission(subscriptionPermission, vg.Next(), nil)
require.True(t, um.hasPermissionLocked("audio", "p1"))
require.True(t, um.hasPermissionLocked("video", "p1"))
require.True(t, um.hasPermissionLocked("screen", "p1"))
require.True(t, um.hasPermissionLocked("audio", "p2"))
require.False(t, um.hasPermissionLocked("video", "p2"))
require.False(t, um.hasPermissionLocked("screen", "p2"))
require.False(t, um.hasPermissionLocked("audio", "p3"))
require.True(t, um.hasPermissionLocked("video", "p3"))
require.False(t, um.hasPermissionLocked("screen", "p3"))
// add a new track after restrictive permissions are set
trw := &typesfakes.FakeMediaTrack{}
trw.IDReturns("watch")
um.publishedTracks["watch"] = trw
require.True(t, um.hasPermissionLocked("audio", "p1"))
require.True(t, um.hasPermissionLocked("video", "p1"))
require.True(t, um.hasPermissionLocked("screen", "p1"))
require.True(t, um.hasPermissionLocked("watch", "p1"))
require.True(t, um.hasPermissionLocked("audio", "p2"))
require.False(t, um.hasPermissionLocked("video", "p2"))
require.False(t, um.hasPermissionLocked("screen", "p2"))
require.False(t, um.hasPermissionLocked("watch", "p2"))
require.False(t, um.hasPermissionLocked("audio", "p3"))
require.True(t, um.hasPermissionLocked("video", "p3"))
require.False(t, um.hasPermissionLocked("screen", "p3"))
require.False(t, um.hasPermissionLocked("watch", "p3"))
})
}