@quenty/permissionprovider 14.42.0 → 14.44.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/package.json +13 -12
- package/src/Server/PermissionProviderUtils.spec.lua +118 -0
- package/src/Server/PermissionService.lua +6 -5
- package/src/Server/PermissionService.spec.lua +203 -0
- package/src/Server/Providers/BasePermissionProvider.lua +7 -6
- package/src/Server/Providers/BasePermissionProvider.spec.lua +136 -0
- package/src/Server/Providers/CreatorPermissionProvider.lua +6 -2
- package/src/Server/Providers/CreatorPermissionProvider.spec.lua +130 -0
- package/src/Server/Providers/GroupPermissionProvider.lua +17 -12
- package/src/Server/Providers/GroupPermissionProvider.spec.lua +180 -0
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,16 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
# [14.44.0](https://github.com/Quenty/NevermoreEngine/compare/@quenty/permissionprovider@14.43.0...@quenty/permissionprovider@14.44.0) (2026-07-23)
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
- Add baseline player-mock and support across Nevermore for mocked players. ([567d121](https://github.com/Quenty/NevermoreEngine/commit/567d121ffc014b42391554088189a1a6296dda83))
|
|
11
|
+
|
|
12
|
+
# [14.43.0](https://github.com/Quenty/NevermoreEngine/compare/@quenty/permissionprovider@14.42.0...@quenty/permissionprovider@14.43.0) (2026-07-22)
|
|
13
|
+
|
|
14
|
+
**Note:** Version bump only for package @quenty/permissionprovider
|
|
15
|
+
|
|
6
16
|
# [14.42.0](https://github.com/Quenty/NevermoreEngine/compare/@quenty/permissionprovider@14.41.0...@quenty/permissionprovider@14.42.0) (2026-07-18)
|
|
7
17
|
|
|
8
18
|
### Bug Fixes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quenty/permissionprovider",
|
|
3
|
-
"version": "14.
|
|
3
|
+
"version": "14.44.0",
|
|
4
4
|
"description": "Permission provider for Roblox, including authenticating against group membership",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"Roblox",
|
|
@@ -28,23 +28,24 @@
|
|
|
28
28
|
"Quenty"
|
|
29
29
|
],
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@quenty/baseobject": "10.
|
|
32
|
-
"@quenty/brio": "14.
|
|
31
|
+
"@quenty/baseobject": "10.15.0",
|
|
32
|
+
"@quenty/brio": "14.33.0",
|
|
33
33
|
"@quenty/enums": "1.3.0",
|
|
34
|
-
"@quenty/grouputils": "10.
|
|
34
|
+
"@quenty/grouputils": "10.23.0",
|
|
35
35
|
"@quenty/loader": "10.11.0",
|
|
36
|
-
"@quenty/maid": "3.
|
|
37
|
-
"@quenty/nevermore-test-runner": "1.
|
|
38
|
-
"@quenty/
|
|
39
|
-
"@quenty/
|
|
40
|
-
"@quenty/
|
|
41
|
-
"@quenty/
|
|
42
|
-
"@quenty/
|
|
36
|
+
"@quenty/maid": "3.11.0",
|
|
37
|
+
"@quenty/nevermore-test-runner": "1.5.0",
|
|
38
|
+
"@quenty/playermock": "1.1.0",
|
|
39
|
+
"@quenty/playerutils": "8.36.0",
|
|
40
|
+
"@quenty/promise": "10.21.0",
|
|
41
|
+
"@quenty/remoting": "12.36.0",
|
|
42
|
+
"@quenty/rx": "13.31.0",
|
|
43
|
+
"@quenty/servicebag": "11.20.0",
|
|
43
44
|
"@quenty/table": "3.9.2",
|
|
44
45
|
"@quentystudios/jest-lua": "3.10.0-quenty.2"
|
|
45
46
|
},
|
|
46
47
|
"publishConfig": {
|
|
47
48
|
"access": "public"
|
|
48
49
|
},
|
|
49
|
-
"gitHead": "
|
|
50
|
+
"gitHead": "1de37218a2bedb8e3f8614a2e09bba9eddc812da"
|
|
50
51
|
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
--!strict
|
|
2
|
+
--[[
|
|
3
|
+
@class PermissionProviderUtils.spec.lua
|
|
4
|
+
]]
|
|
5
|
+
|
|
6
|
+
local require = require(script.Parent.loader).load(script)
|
|
7
|
+
|
|
8
|
+
local Jest = require("Jest")
|
|
9
|
+
local PermissionProviderConstants = require("PermissionProviderConstants")
|
|
10
|
+
local PermissionProviderUtils = require("PermissionProviderUtils")
|
|
11
|
+
|
|
12
|
+
local describe = Jest.Globals.describe
|
|
13
|
+
local expect = Jest.Globals.expect
|
|
14
|
+
local it = Jest.Globals.it
|
|
15
|
+
|
|
16
|
+
describe("PermissionProviderUtils.createGroupRankConfig", function()
|
|
17
|
+
it("should create a group rank config with the given ranks", function()
|
|
18
|
+
local config = PermissionProviderUtils.createGroupRankConfig({
|
|
19
|
+
groupId = 12345,
|
|
20
|
+
minAdminRequiredRank = 250,
|
|
21
|
+
minCreatorRequiredRank = 254,
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
expect(config.type).toEqual(PermissionProviderConstants.GROUP_RANK_CONFIG_TYPE)
|
|
25
|
+
expect(config.groupId).toEqual(12345)
|
|
26
|
+
expect(config.minAdminRequiredRank).toEqual(250)
|
|
27
|
+
expect(config.minCreatorRequiredRank).toEqual(254)
|
|
28
|
+
end)
|
|
29
|
+
|
|
30
|
+
it("should default the remote function name", function()
|
|
31
|
+
local config = PermissionProviderUtils.createGroupRankConfig({
|
|
32
|
+
groupId = 12345,
|
|
33
|
+
minAdminRequiredRank = 250,
|
|
34
|
+
minCreatorRequiredRank = 254,
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
expect(config.remoteFunctionName).toEqual(PermissionProviderConstants.DEFAULT_REMOTE_FUNCTION_NAME)
|
|
38
|
+
end)
|
|
39
|
+
|
|
40
|
+
it("should honor a custom remote function name", function()
|
|
41
|
+
local config = PermissionProviderUtils.createGroupRankConfig({
|
|
42
|
+
groupId = 12345,
|
|
43
|
+
minAdminRequiredRank = 250,
|
|
44
|
+
minCreatorRequiredRank = 254,
|
|
45
|
+
remoteFunctionName = "CustomRemoteFunction",
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
expect(config.remoteFunctionName).toEqual("CustomRemoteFunction")
|
|
49
|
+
end)
|
|
50
|
+
|
|
51
|
+
it("should reject a missing groupId", function()
|
|
52
|
+
expect(function()
|
|
53
|
+
PermissionProviderUtils.createGroupRankConfig({
|
|
54
|
+
minAdminRequiredRank = 250,
|
|
55
|
+
minCreatorRequiredRank = 254,
|
|
56
|
+
} :: any)
|
|
57
|
+
end).toThrow("Bad groupId")
|
|
58
|
+
end)
|
|
59
|
+
|
|
60
|
+
it("should reject a missing minCreatorRequiredRank", function()
|
|
61
|
+
expect(function()
|
|
62
|
+
PermissionProviderUtils.createGroupRankConfig({
|
|
63
|
+
groupId = 12345,
|
|
64
|
+
minAdminRequiredRank = 250,
|
|
65
|
+
} :: any)
|
|
66
|
+
end).toThrow("Bad minCreatorRequiredRank")
|
|
67
|
+
end)
|
|
68
|
+
|
|
69
|
+
it("should reject a missing minAdminRequiredRank", function()
|
|
70
|
+
expect(function()
|
|
71
|
+
PermissionProviderUtils.createGroupRankConfig({
|
|
72
|
+
groupId = 12345,
|
|
73
|
+
minCreatorRequiredRank = 254,
|
|
74
|
+
} :: any)
|
|
75
|
+
end).toThrow("Bad minAdminRequiredRank")
|
|
76
|
+
end)
|
|
77
|
+
end)
|
|
78
|
+
|
|
79
|
+
describe("PermissionProviderUtils.createSingleUserConfig", function()
|
|
80
|
+
it("should create a single user config", function()
|
|
81
|
+
local config = PermissionProviderUtils.createSingleUserConfig({
|
|
82
|
+
userId = 12345,
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
expect(config.type).toEqual(PermissionProviderConstants.SINGLE_USER_CONFIG_TYPE)
|
|
86
|
+
expect(config.userId).toEqual(12345)
|
|
87
|
+
expect(config.remoteFunctionName).toEqual(PermissionProviderConstants.DEFAULT_REMOTE_FUNCTION_NAME)
|
|
88
|
+
end)
|
|
89
|
+
|
|
90
|
+
it("should honor a custom remote function name", function()
|
|
91
|
+
local config = PermissionProviderUtils.createSingleUserConfig({
|
|
92
|
+
userId = 12345,
|
|
93
|
+
remoteFunctionName = "CustomRemoteFunction",
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
expect(config.remoteFunctionName).toEqual("CustomRemoteFunction")
|
|
97
|
+
end)
|
|
98
|
+
|
|
99
|
+
it("should reject a missing userId", function()
|
|
100
|
+
expect(function()
|
|
101
|
+
PermissionProviderUtils.createSingleUserConfig({} :: any)
|
|
102
|
+
end).toThrow("Bad userId")
|
|
103
|
+
end)
|
|
104
|
+
end)
|
|
105
|
+
|
|
106
|
+
describe("PermissionProviderUtils.createConfigFromGame", function()
|
|
107
|
+
it("should create a config matching the game's creator type", function()
|
|
108
|
+
local config = PermissionProviderUtils.createConfigFromGame()
|
|
109
|
+
|
|
110
|
+
if game.CreatorType == Enum.CreatorType.Group then
|
|
111
|
+
expect(config.type).toEqual(PermissionProviderConstants.GROUP_RANK_CONFIG_TYPE)
|
|
112
|
+
expect((config :: any).groupId).toEqual(game.CreatorId)
|
|
113
|
+
else
|
|
114
|
+
expect(config.type).toEqual(PermissionProviderConstants.SINGLE_USER_CONFIG_TYPE)
|
|
115
|
+
expect((config :: any).userId).toEqual(game.CreatorId)
|
|
116
|
+
end
|
|
117
|
+
end)
|
|
118
|
+
end)
|
|
@@ -22,6 +22,7 @@ local Observable = require("Observable")
|
|
|
22
22
|
local PermissionLevel = require("PermissionLevel")
|
|
23
23
|
local PermissionProviderConstants = require("PermissionProviderConstants")
|
|
24
24
|
local PermissionProviderUtils = require("PermissionProviderUtils")
|
|
25
|
+
local PlayerMock = require("PlayerMock")
|
|
25
26
|
local Promise = require("Promise")
|
|
26
27
|
local Rx = require("Rx")
|
|
27
28
|
local RxBrioUtils = require("RxBrioUtils")
|
|
@@ -67,9 +68,9 @@ function PermissionService.SetProviderFromConfig(
|
|
|
67
68
|
assert(not self._provider, "Already have provider set")
|
|
68
69
|
|
|
69
70
|
if config.type == PermissionProviderConstants.GROUP_RANK_CONFIG_TYPE then
|
|
70
|
-
self._provider = GroupPermissionProvider.new(config)
|
|
71
|
+
self._provider = self._maid:Add(GroupPermissionProvider.new(config))
|
|
71
72
|
elseif config.type == PermissionProviderConstants.SINGLE_USER_CONFIG_TYPE then
|
|
72
|
-
self._provider = CreatorPermissionProvider.new(config)
|
|
73
|
+
self._provider = self._maid:Add(CreatorPermissionProvider.new(config))
|
|
73
74
|
else
|
|
74
75
|
error("Bad provider")
|
|
75
76
|
end
|
|
@@ -106,7 +107,7 @@ end
|
|
|
106
107
|
@return Promise<boolean>
|
|
107
108
|
]=]
|
|
108
109
|
function PermissionService.PromiseIsAdmin(self: PermissionService, player: Player): Promise.Promise<boolean>
|
|
109
|
-
assert(typeof(player) == "Instance" and player:IsA("Player"), "bad player")
|
|
110
|
+
assert((typeof(player) == "Instance" and player:IsA("Player")) or PlayerMock.isMock(player), "bad player")
|
|
110
111
|
|
|
111
112
|
return self:PromiseIsPermissionLevel(player, PermissionLevel.ADMIN)
|
|
112
113
|
end
|
|
@@ -117,7 +118,7 @@ end
|
|
|
117
118
|
@return Promise<boolean>
|
|
118
119
|
]=]
|
|
119
120
|
function PermissionService.PromiseIsCreator(self: PermissionService, player: Player): Promise.Promise<boolean>
|
|
120
|
-
assert(typeof(player) == "Instance" and player:IsA("Player"), "bad player")
|
|
121
|
+
assert((typeof(player) == "Instance" and player:IsA("Player")) or PlayerMock.isMock(player), "bad player")
|
|
121
122
|
|
|
122
123
|
return self:PromiseIsPermissionLevel(player, PermissionLevel.CREATOR)
|
|
123
124
|
end
|
|
@@ -133,7 +134,7 @@ function PermissionService.PromiseIsPermissionLevel(
|
|
|
133
134
|
player: Player,
|
|
134
135
|
permissionLevel: PermissionLevel.PermissionLevel
|
|
135
136
|
): Promise.Promise<boolean>
|
|
136
|
-
assert(typeof(player) == "Instance" and player:IsA("Player"), "bad player")
|
|
137
|
+
assert((typeof(player) == "Instance" and player:IsA("Player")) or PlayerMock.isMock(player), "bad player")
|
|
137
138
|
assert(PermissionLevel:IsValue(permissionLevel), "Bad permissionLevel")
|
|
138
139
|
|
|
139
140
|
if RunService:IsStudio() then
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
--!strict
|
|
2
|
+
--[[
|
|
3
|
+
Coverage for PermissionService's ServiceBag-driven lifecycle and permission queries, with a
|
|
4
|
+
single-user (creator) provider config and PlayerMock players.
|
|
5
|
+
|
|
6
|
+
The deny cases assume RunService:IsStudio() == false (true in the cloud test runner); in Studio
|
|
7
|
+
the service short-circuits every permission query to true.
|
|
8
|
+
|
|
9
|
+
@class PermissionService.spec.lua
|
|
10
|
+
]]
|
|
11
|
+
|
|
12
|
+
local require = require(script.Parent.loader).load(script)
|
|
13
|
+
|
|
14
|
+
local Jest = require("Jest")
|
|
15
|
+
local Maid = require("Maid")
|
|
16
|
+
local PermissionLevel = require("PermissionLevel")
|
|
17
|
+
local PermissionProviderUtils = require("PermissionProviderUtils")
|
|
18
|
+
local PermissionService = require("PermissionService")
|
|
19
|
+
local PlayerMock = require("PlayerMock")
|
|
20
|
+
local PromiseTestUtils = require("PromiseTestUtils")
|
|
21
|
+
local ServiceBag = require("ServiceBag")
|
|
22
|
+
|
|
23
|
+
local describe = Jest.Globals.describe
|
|
24
|
+
local expect = Jest.Globals.expect
|
|
25
|
+
local it = Jest.Globals.it
|
|
26
|
+
|
|
27
|
+
local CREATOR_USER_ID = 12345
|
|
28
|
+
|
|
29
|
+
local remoteNameCounter = 0
|
|
30
|
+
|
|
31
|
+
local function setup()
|
|
32
|
+
local maid = Maid.new()
|
|
33
|
+
local serviceBag = maid:Add(ServiceBag.new())
|
|
34
|
+
local permissionService: PermissionService.PermissionService = serviceBag:GetService(PermissionService) :: any
|
|
35
|
+
serviceBag:Init()
|
|
36
|
+
|
|
37
|
+
-- Unique remote function name so services from separate tests never share the global remote.
|
|
38
|
+
remoteNameCounter += 1
|
|
39
|
+
local remoteFunctionName = string.format("PermissionServiceSpecRemote%d", remoteNameCounter)
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
serviceBag = serviceBag,
|
|
43
|
+
permissionService = permissionService,
|
|
44
|
+
singleUserConfig = function(userId: number)
|
|
45
|
+
return PermissionProviderUtils.createSingleUserConfig({
|
|
46
|
+
userId = userId,
|
|
47
|
+
remoteFunctionName = remoteFunctionName,
|
|
48
|
+
})
|
|
49
|
+
end,
|
|
50
|
+
fakePlayer = function(userId: number): Player
|
|
51
|
+
return maid:Add(PlayerMock.new({ UserId = userId }))
|
|
52
|
+
end,
|
|
53
|
+
awaitBool = function(promise: any): boolean
|
|
54
|
+
expect(PromiseTestUtils.awaitSettled(promise, 5)).toEqual(true)
|
|
55
|
+
local ok, value = promise:Yield()
|
|
56
|
+
expect(ok).toEqual(true)
|
|
57
|
+
return value
|
|
58
|
+
end,
|
|
59
|
+
destroy = function(_self)
|
|
60
|
+
maid:DoCleaning()
|
|
61
|
+
end,
|
|
62
|
+
}
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
describe("PermissionService initialization", function()
|
|
66
|
+
it("should initialize and start with a configured provider", function()
|
|
67
|
+
local controller = setup()
|
|
68
|
+
controller.permissionService:SetProviderFromConfig(controller.singleUserConfig(CREATOR_USER_ID))
|
|
69
|
+
|
|
70
|
+
expect(function()
|
|
71
|
+
controller.serviceBag:Start()
|
|
72
|
+
end).never.toThrow()
|
|
73
|
+
|
|
74
|
+
controller:destroy()
|
|
75
|
+
end)
|
|
76
|
+
|
|
77
|
+
it("should start with a provider derived from the game when none is configured", function()
|
|
78
|
+
local controller = setup()
|
|
79
|
+
|
|
80
|
+
expect(function()
|
|
81
|
+
controller.serviceBag:Start()
|
|
82
|
+
end).never.toThrow()
|
|
83
|
+
|
|
84
|
+
controller:destroy()
|
|
85
|
+
end)
|
|
86
|
+
|
|
87
|
+
it("should resolve the permission provider after start", function()
|
|
88
|
+
local controller = setup()
|
|
89
|
+
controller.permissionService:SetProviderFromConfig(controller.singleUserConfig(CREATOR_USER_ID))
|
|
90
|
+
|
|
91
|
+
local promise = controller.permissionService:PromisePermissionProvider()
|
|
92
|
+
expect(promise:IsPending()).toEqual(true)
|
|
93
|
+
|
|
94
|
+
controller.serviceBag:Start()
|
|
95
|
+
|
|
96
|
+
expect(PromiseTestUtils.awaitSettled(promise, 5)).toEqual(true)
|
|
97
|
+
local ok, provider = promise:Yield()
|
|
98
|
+
expect(ok).toEqual(true)
|
|
99
|
+
expect((provider :: any).ClassName).toEqual("CreatorPermissionProvider")
|
|
100
|
+
|
|
101
|
+
controller:destroy()
|
|
102
|
+
end)
|
|
103
|
+
end)
|
|
104
|
+
|
|
105
|
+
describe("PermissionService.SetProviderFromConfig", function()
|
|
106
|
+
it("should reject a second provider config", function()
|
|
107
|
+
local controller = setup()
|
|
108
|
+
controller.permissionService:SetProviderFromConfig(controller.singleUserConfig(CREATOR_USER_ID))
|
|
109
|
+
|
|
110
|
+
expect(function()
|
|
111
|
+
controller.permissionService:SetProviderFromConfig(controller.singleUserConfig(CREATOR_USER_ID))
|
|
112
|
+
end).toThrow("Already have provider set")
|
|
113
|
+
|
|
114
|
+
controller:destroy()
|
|
115
|
+
end)
|
|
116
|
+
|
|
117
|
+
it("should reject an unknown config type", function()
|
|
118
|
+
local controller = setup()
|
|
119
|
+
|
|
120
|
+
expect(function()
|
|
121
|
+
controller.permissionService:SetProviderFromConfig({ type = "UnknownConfigType" } :: any)
|
|
122
|
+
end).toThrow("Bad provider")
|
|
123
|
+
|
|
124
|
+
controller:destroy()
|
|
125
|
+
end)
|
|
126
|
+
end)
|
|
127
|
+
|
|
128
|
+
describe("PermissionService permission queries", function()
|
|
129
|
+
it("should treat the configured user as an admin", function()
|
|
130
|
+
local controller = setup()
|
|
131
|
+
controller.permissionService:SetProviderFromConfig(controller.singleUserConfig(CREATOR_USER_ID))
|
|
132
|
+
controller.serviceBag:Start()
|
|
133
|
+
|
|
134
|
+
local player = controller.fakePlayer(CREATOR_USER_ID)
|
|
135
|
+
expect(controller.awaitBool(controller.permissionService:PromiseIsAdmin(player))).toEqual(true)
|
|
136
|
+
|
|
137
|
+
controller:destroy()
|
|
138
|
+
end)
|
|
139
|
+
|
|
140
|
+
it("should treat the configured user as a creator", function()
|
|
141
|
+
local controller = setup()
|
|
142
|
+
controller.permissionService:SetProviderFromConfig(controller.singleUserConfig(CREATOR_USER_ID))
|
|
143
|
+
controller.serviceBag:Start()
|
|
144
|
+
|
|
145
|
+
local player = controller.fakePlayer(CREATOR_USER_ID)
|
|
146
|
+
expect(controller.awaitBool(controller.permissionService:PromiseIsCreator(player))).toEqual(true)
|
|
147
|
+
|
|
148
|
+
controller:destroy()
|
|
149
|
+
end)
|
|
150
|
+
|
|
151
|
+
it("should deny another user admin permission", function()
|
|
152
|
+
local controller = setup()
|
|
153
|
+
controller.permissionService:SetProviderFromConfig(controller.singleUserConfig(CREATOR_USER_ID))
|
|
154
|
+
controller.serviceBag:Start()
|
|
155
|
+
|
|
156
|
+
local player = controller.fakePlayer(CREATOR_USER_ID + 1)
|
|
157
|
+
expect(controller.awaitBool(controller.permissionService:PromiseIsAdmin(player))).toEqual(false)
|
|
158
|
+
|
|
159
|
+
controller:destroy()
|
|
160
|
+
end)
|
|
161
|
+
|
|
162
|
+
it("should reject a non-player value", function()
|
|
163
|
+
local controller = setup()
|
|
164
|
+
controller.permissionService:SetProviderFromConfig(controller.singleUserConfig(CREATOR_USER_ID))
|
|
165
|
+
controller.serviceBag:Start()
|
|
166
|
+
|
|
167
|
+
expect(function()
|
|
168
|
+
controller.permissionService:PromiseIsAdmin(nil :: any)
|
|
169
|
+
end).toThrow("bad player")
|
|
170
|
+
|
|
171
|
+
controller:destroy()
|
|
172
|
+
end)
|
|
173
|
+
|
|
174
|
+
it("should reject an invalid permission level", function()
|
|
175
|
+
local controller = setup()
|
|
176
|
+
controller.permissionService:SetProviderFromConfig(controller.singleUserConfig(CREATOR_USER_ID))
|
|
177
|
+
controller.serviceBag:Start()
|
|
178
|
+
|
|
179
|
+
local player = controller.fakePlayer(CREATOR_USER_ID)
|
|
180
|
+
expect(function()
|
|
181
|
+
controller.permissionService:PromiseIsPermissionLevel(player, "not-a-level" :: any)
|
|
182
|
+
end).toThrow("Bad permissionLevel")
|
|
183
|
+
|
|
184
|
+
controller:destroy()
|
|
185
|
+
end)
|
|
186
|
+
end)
|
|
187
|
+
|
|
188
|
+
describe("PermissionService.ObservePermissionedPlayersBrio", function()
|
|
189
|
+
it("should subscribe and unsubscribe without error on an empty server", function()
|
|
190
|
+
local controller = setup()
|
|
191
|
+
controller.permissionService:SetProviderFromConfig(controller.singleUserConfig(CREATOR_USER_ID))
|
|
192
|
+
controller.serviceBag:Start()
|
|
193
|
+
|
|
194
|
+
expect(function()
|
|
195
|
+
local subscription = controller.permissionService
|
|
196
|
+
:ObservePermissionedPlayersBrio(PermissionLevel.ADMIN)
|
|
197
|
+
:Subscribe(function() end)
|
|
198
|
+
subscription:Destroy()
|
|
199
|
+
end).never.toThrow()
|
|
200
|
+
|
|
201
|
+
controller:destroy()
|
|
202
|
+
end)
|
|
203
|
+
end)
|
|
@@ -11,6 +11,7 @@ local BaseObject = require("BaseObject")
|
|
|
11
11
|
local GetRemoteFunction = require("GetRemoteFunction")
|
|
12
12
|
local PermissionLevel = require("PermissionLevel")
|
|
13
13
|
local PermissionProviderUtils = require("PermissionProviderUtils")
|
|
14
|
+
local PlayerMock = require("PlayerMock")
|
|
14
15
|
local Promise = require("Promise")
|
|
15
16
|
local Table = require("Table")
|
|
16
17
|
|
|
@@ -68,7 +69,7 @@ function BasePermissionProvider.PromiseIsPermissionLevel(
|
|
|
68
69
|
player: Player,
|
|
69
70
|
permissionLevel: PermissionLevel.PermissionLevel
|
|
70
71
|
): Promise.Promise<boolean>
|
|
71
|
-
assert(typeof(player) == "Instance" and player:IsA("Player"), "Bad player")
|
|
72
|
+
assert((typeof(player) == "Instance" and player:IsA("Player")) or PlayerMock.isMock(player), "Bad player")
|
|
72
73
|
assert(PermissionLevel:IsValue(permissionLevel))
|
|
73
74
|
|
|
74
75
|
error("Not implemented")
|
|
@@ -85,7 +86,7 @@ function BasePermissionProvider.IsPermissionLevel(
|
|
|
85
86
|
player: Player,
|
|
86
87
|
permissionLevel: PermissionLevel.PermissionLevel
|
|
87
88
|
): boolean
|
|
88
|
-
assert(typeof(player) == "Instance" and player:IsA("Player"), "Bad player")
|
|
89
|
+
assert((typeof(player) == "Instance" and player:IsA("Player")) or PlayerMock.isMock(player), "Bad player")
|
|
89
90
|
assert(PermissionLevel:IsValue(permissionLevel))
|
|
90
91
|
|
|
91
92
|
local promise = self:PromiseIsPermissionLevel(player, permissionLevel)
|
|
@@ -108,7 +109,7 @@ end
|
|
|
108
109
|
@return Promise<boolean>
|
|
109
110
|
]=]
|
|
110
111
|
function BasePermissionProvider.PromiseIsCreator(self: BasePermissionProvider, player: Player): Promise.Promise<boolean>
|
|
111
|
-
assert(typeof(player) == "Instance" and player:IsA("Player"), "Bad player")
|
|
112
|
+
assert((typeof(player) == "Instance" and player:IsA("Player")) or PlayerMock.isMock(player), "Bad player")
|
|
112
113
|
|
|
113
114
|
return self:PromiseIsPermissionLevel(player, PermissionLevel.CREATOR)
|
|
114
115
|
end
|
|
@@ -119,7 +120,7 @@ end
|
|
|
119
120
|
@return Promise<boolean>
|
|
120
121
|
]=]
|
|
121
122
|
function BasePermissionProvider.PromiseIsAdmin(self: BasePermissionProvider, player: Player): Promise.Promise<boolean>
|
|
122
|
-
assert(typeof(player) == "Instance" and player:IsA("Player"), "Bad player")
|
|
123
|
+
assert((typeof(player) == "Instance" and player:IsA("Player")) or PlayerMock.isMock(player), "Bad player")
|
|
123
124
|
|
|
124
125
|
return self:PromiseIsPermissionLevel(player, PermissionLevel.ADMIN)
|
|
125
126
|
end
|
|
@@ -135,7 +136,7 @@ end
|
|
|
135
136
|
@return boolean
|
|
136
137
|
]=]
|
|
137
138
|
function BasePermissionProvider.IsCreator(self: BasePermissionProvider, player: Player): boolean
|
|
138
|
-
assert(typeof(player) == "Instance" and player:IsA("Player"), "Bad player")
|
|
139
|
+
assert((typeof(player) == "Instance" and player:IsA("Player")) or PlayerMock.isMock(player), "Bad player")
|
|
139
140
|
|
|
140
141
|
return self:IsPermissionLevel(player, PermissionLevel.CREATOR)
|
|
141
142
|
end
|
|
@@ -151,7 +152,7 @@ end
|
|
|
151
152
|
@return boolean
|
|
152
153
|
]=]
|
|
153
154
|
function BasePermissionProvider.IsAdmin(self: BasePermissionProvider, player: Player): boolean
|
|
154
|
-
assert(typeof(player) == "Instance" and player:IsA("Player"), "Bad player")
|
|
155
|
+
assert((typeof(player) == "Instance" and player:IsA("Player")) or PlayerMock.isMock(player), "Bad player")
|
|
155
156
|
|
|
156
157
|
return self:IsPermissionLevel(player, PermissionLevel.ADMIN)
|
|
157
158
|
end
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
--!strict
|
|
2
|
+
--[[
|
|
3
|
+
@class BasePermissionProvider.spec.lua
|
|
4
|
+
]]
|
|
5
|
+
|
|
6
|
+
local require = require(script.Parent.loader).load(script)
|
|
7
|
+
|
|
8
|
+
local BasePermissionProvider = require("BasePermissionProvider")
|
|
9
|
+
local Jest = require("Jest")
|
|
10
|
+
local Maid = require("Maid")
|
|
11
|
+
local PermissionLevel = require("PermissionLevel")
|
|
12
|
+
local PermissionProviderUtils = require("PermissionProviderUtils")
|
|
13
|
+
local PlayerMock = require("PlayerMock")
|
|
14
|
+
local Promise = require("Promise")
|
|
15
|
+
|
|
16
|
+
local describe = Jest.Globals.describe
|
|
17
|
+
local expect = Jest.Globals.expect
|
|
18
|
+
local it = Jest.Globals.it
|
|
19
|
+
|
|
20
|
+
local function createStubProvider(maid: Maid.Maid, promiseFactory: () -> any)
|
|
21
|
+
local StubPermissionProvider = setmetatable({}, BasePermissionProvider)
|
|
22
|
+
StubPermissionProvider.ClassName = "StubPermissionProvider"
|
|
23
|
+
StubPermissionProvider.__index = StubPermissionProvider
|
|
24
|
+
|
|
25
|
+
local queriedLevels = {}
|
|
26
|
+
|
|
27
|
+
function StubPermissionProvider.PromiseIsPermissionLevel(_self: any, _player: any, permissionLevel: any)
|
|
28
|
+
table.insert(queriedLevels, permissionLevel)
|
|
29
|
+
return promiseFactory()
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
local config = PermissionProviderUtils.createSingleUserConfig({ userId = 12345 })
|
|
33
|
+
local provider = maid:Add(setmetatable(BasePermissionProvider.new(config) :: any, StubPermissionProvider))
|
|
34
|
+
|
|
35
|
+
return provider, queriedLevels
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
describe("BasePermissionProvider.new", function()
|
|
39
|
+
it("should reject a nil config", function()
|
|
40
|
+
expect(function()
|
|
41
|
+
BasePermissionProvider.new(nil :: any)
|
|
42
|
+
end).toThrow("Bad config")
|
|
43
|
+
end)
|
|
44
|
+
|
|
45
|
+
it("should reject a config without a remote function name", function()
|
|
46
|
+
expect(function()
|
|
47
|
+
BasePermissionProvider.new({} :: any)
|
|
48
|
+
end).toThrow("remoteFunctionName")
|
|
49
|
+
end)
|
|
50
|
+
end)
|
|
51
|
+
|
|
52
|
+
describe("BasePermissionProvider.PromiseIsPermissionLevel", function()
|
|
53
|
+
it("should error as not implemented on the base class", function()
|
|
54
|
+
local maid = Maid.new()
|
|
55
|
+
local provider =
|
|
56
|
+
maid:Add(BasePermissionProvider.new(PermissionProviderUtils.createSingleUserConfig({ userId = 12345 })))
|
|
57
|
+
local player = maid:Add(PlayerMock.new({ UserId = 12345 }))
|
|
58
|
+
|
|
59
|
+
expect(function()
|
|
60
|
+
provider:PromiseIsPermissionLevel(player, PermissionLevel.ADMIN)
|
|
61
|
+
end).toThrow("Not implemented")
|
|
62
|
+
|
|
63
|
+
maid:DoCleaning()
|
|
64
|
+
end)
|
|
65
|
+
|
|
66
|
+
it("should reject a non-player value", function()
|
|
67
|
+
local maid = Maid.new()
|
|
68
|
+
local provider =
|
|
69
|
+
maid:Add(BasePermissionProvider.new(PermissionProviderUtils.createSingleUserConfig({ userId = 12345 })))
|
|
70
|
+
|
|
71
|
+
expect(function()
|
|
72
|
+
provider:PromiseIsPermissionLevel(nil :: any, PermissionLevel.ADMIN)
|
|
73
|
+
end).toThrow("Bad player")
|
|
74
|
+
|
|
75
|
+
maid:DoCleaning()
|
|
76
|
+
end)
|
|
77
|
+
end)
|
|
78
|
+
|
|
79
|
+
describe("BasePermissionProvider.IsPermissionLevel", function()
|
|
80
|
+
it("should return false while the underlying promise is pending", function()
|
|
81
|
+
local maid = Maid.new()
|
|
82
|
+
local provider = createStubProvider(maid, function()
|
|
83
|
+
return maid:Add(Promise.new())
|
|
84
|
+
end)
|
|
85
|
+
local player = maid:Add(PlayerMock.new({ UserId = 12345 }))
|
|
86
|
+
|
|
87
|
+
expect(provider:IsPermissionLevel(player, PermissionLevel.ADMIN)).toEqual(false)
|
|
88
|
+
|
|
89
|
+
maid:DoCleaning()
|
|
90
|
+
end)
|
|
91
|
+
|
|
92
|
+
it("should return the resolved value when the promise settles synchronously", function()
|
|
93
|
+
local maid = Maid.new()
|
|
94
|
+
local provider = createStubProvider(maid, function()
|
|
95
|
+
return Promise.resolved(true)
|
|
96
|
+
end)
|
|
97
|
+
local player = maid:Add(PlayerMock.new({ UserId = 12345 }))
|
|
98
|
+
|
|
99
|
+
expect(provider:IsPermissionLevel(player, PermissionLevel.ADMIN)).toEqual(true)
|
|
100
|
+
expect(provider:IsCreator(player)).toEqual(true)
|
|
101
|
+
expect(provider:IsAdmin(player)).toEqual(true)
|
|
102
|
+
|
|
103
|
+
maid:DoCleaning()
|
|
104
|
+
end)
|
|
105
|
+
|
|
106
|
+
it("should return false when the underlying promise rejects", function()
|
|
107
|
+
local maid = Maid.new()
|
|
108
|
+
local provider = createStubProvider(maid, function()
|
|
109
|
+
local promise = Promise.rejected("simulated failure")
|
|
110
|
+
promise:Catch(function() end)
|
|
111
|
+
return promise
|
|
112
|
+
end)
|
|
113
|
+
local player = maid:Add(PlayerMock.new({ UserId = 12345 }))
|
|
114
|
+
|
|
115
|
+
expect(provider:IsPermissionLevel(player, PermissionLevel.ADMIN)).toEqual(false)
|
|
116
|
+
|
|
117
|
+
maid:DoCleaning()
|
|
118
|
+
end)
|
|
119
|
+
end)
|
|
120
|
+
|
|
121
|
+
describe("BasePermissionProvider promise wrappers", function()
|
|
122
|
+
it("should route PromiseIsAdmin and PromiseIsCreator through the matching level", function()
|
|
123
|
+
local maid = Maid.new()
|
|
124
|
+
local provider, queriedLevels = createStubProvider(maid, function()
|
|
125
|
+
return Promise.resolved(true)
|
|
126
|
+
end)
|
|
127
|
+
local player = maid:Add(PlayerMock.new({ UserId = 12345 }))
|
|
128
|
+
|
|
129
|
+
provider:PromiseIsAdmin(player)
|
|
130
|
+
provider:PromiseIsCreator(player)
|
|
131
|
+
|
|
132
|
+
expect(queriedLevels).toEqual({ PermissionLevel.ADMIN, PermissionLevel.CREATOR } :: { string })
|
|
133
|
+
|
|
134
|
+
maid:DoCleaning()
|
|
135
|
+
end)
|
|
136
|
+
end)
|
|
@@ -14,6 +14,7 @@ local BasePermissionProvider = require("BasePermissionProvider")
|
|
|
14
14
|
local PermissionLevel = require("PermissionLevel")
|
|
15
15
|
local PermissionProviderConstants = require("PermissionProviderConstants")
|
|
16
16
|
local PermissionProviderUtils = require("PermissionProviderUtils")
|
|
17
|
+
local PlayerMock = require("PlayerMock")
|
|
17
18
|
local Promise = require("Promise")
|
|
18
19
|
|
|
19
20
|
local CreatorPermissionProvider = setmetatable({}, BasePermissionProvider)
|
|
@@ -56,11 +57,14 @@ function CreatorPermissionProvider.PromiseIsPermissionLevel(
|
|
|
56
57
|
player: Player,
|
|
57
58
|
permissionLevel: PermissionLevel.PermissionLevel
|
|
58
59
|
): Promise.Promise<boolean>
|
|
59
|
-
assert(typeof(player) == "Instance" and player:IsA("Player"), "Bad player")
|
|
60
|
+
assert((typeof(player) == "Instance" and player:IsA("Player")) or PlayerMock.isMock(player), "Bad player")
|
|
60
61
|
assert(PermissionLevel:IsValue(permissionLevel))
|
|
61
62
|
|
|
62
63
|
if permissionLevel == PermissionLevel.ADMIN or permissionLevel == PermissionLevel.CREATOR then
|
|
63
|
-
return Promise.resolved(
|
|
64
|
+
return Promise.resolved(
|
|
65
|
+
(if PlayerMock.isMock(player) then PlayerMock.read(player, "UserId") else player.UserId) == self._userId
|
|
66
|
+
or RunService:IsStudio()
|
|
67
|
+
)
|
|
64
68
|
else
|
|
65
69
|
error("Unknown permissionLevel")
|
|
66
70
|
end
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
--!strict
|
|
2
|
+
--[[
|
|
3
|
+
Coverage for CreatorPermissionProvider: a single configured userId is both admin and creator,
|
|
4
|
+
everyone else is neither. Players are stood in by PlayerMock, whose seeded UserId the provider
|
|
5
|
+
reads through PlayerMock.read.
|
|
6
|
+
|
|
7
|
+
The negative cases assume RunService:IsStudio() == false (true in the cloud test runner); in
|
|
8
|
+
Studio the provider grants everyone permission.
|
|
9
|
+
|
|
10
|
+
@class CreatorPermissionProvider.spec.lua
|
|
11
|
+
]]
|
|
12
|
+
|
|
13
|
+
local require = require(script.Parent.loader).load(script)
|
|
14
|
+
|
|
15
|
+
local CreatorPermissionProvider = require("CreatorPermissionProvider")
|
|
16
|
+
local Jest = require("Jest")
|
|
17
|
+
local Maid = require("Maid")
|
|
18
|
+
local PermissionLevel = require("PermissionLevel")
|
|
19
|
+
local PermissionProviderUtils = require("PermissionProviderUtils")
|
|
20
|
+
local PlayerMock = require("PlayerMock")
|
|
21
|
+
local PromiseTestUtils = require("PromiseTestUtils")
|
|
22
|
+
|
|
23
|
+
local describe = Jest.Globals.describe
|
|
24
|
+
local expect = Jest.Globals.expect
|
|
25
|
+
local it = Jest.Globals.it
|
|
26
|
+
|
|
27
|
+
local CREATOR_USER_ID = 12345
|
|
28
|
+
|
|
29
|
+
local function setup()
|
|
30
|
+
local maid = Maid.new()
|
|
31
|
+
local provider = maid:Add(CreatorPermissionProvider.new(PermissionProviderUtils.createSingleUserConfig({
|
|
32
|
+
userId = CREATOR_USER_ID,
|
|
33
|
+
})))
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
provider = provider,
|
|
37
|
+
fakePlayer = function(userId: number): Player
|
|
38
|
+
return maid:Add(PlayerMock.new({ UserId = userId }))
|
|
39
|
+
end,
|
|
40
|
+
awaitBool = function(promise: any): boolean
|
|
41
|
+
expect(PromiseTestUtils.awaitSettled(promise, 5)).toEqual(true)
|
|
42
|
+
local ok, value = promise:Yield()
|
|
43
|
+
expect(ok).toEqual(true)
|
|
44
|
+
return value
|
|
45
|
+
end,
|
|
46
|
+
destroy = function(_self)
|
|
47
|
+
maid:DoCleaning()
|
|
48
|
+
end,
|
|
49
|
+
}
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
describe("CreatorPermissionProvider.new", function()
|
|
53
|
+
it("should reject a group rank config", function()
|
|
54
|
+
expect(function()
|
|
55
|
+
CreatorPermissionProvider.new(PermissionProviderUtils.createGroupRankConfig({
|
|
56
|
+
groupId = 12345,
|
|
57
|
+
minAdminRequiredRank = 250,
|
|
58
|
+
minCreatorRequiredRank = 254,
|
|
59
|
+
}) :: any)
|
|
60
|
+
end).toThrow("Bad configType")
|
|
61
|
+
end)
|
|
62
|
+
end)
|
|
63
|
+
|
|
64
|
+
describe("CreatorPermissionProvider.PromiseIsPermissionLevel", function()
|
|
65
|
+
it("should treat the configured user as an admin", function()
|
|
66
|
+
local controller = setup()
|
|
67
|
+
local player = controller.fakePlayer(CREATOR_USER_ID)
|
|
68
|
+
|
|
69
|
+
expect(controller.awaitBool(controller.provider:PromiseIsAdmin(player))).toEqual(true)
|
|
70
|
+
|
|
71
|
+
controller:destroy()
|
|
72
|
+
end)
|
|
73
|
+
|
|
74
|
+
it("should treat the configured user as a creator", function()
|
|
75
|
+
local controller = setup()
|
|
76
|
+
local player = controller.fakePlayer(CREATOR_USER_ID)
|
|
77
|
+
|
|
78
|
+
expect(controller.awaitBool(controller.provider:PromiseIsCreator(player))).toEqual(true)
|
|
79
|
+
|
|
80
|
+
controller:destroy()
|
|
81
|
+
end)
|
|
82
|
+
|
|
83
|
+
it("should deny another user admin permission", function()
|
|
84
|
+
local controller = setup()
|
|
85
|
+
local player = controller.fakePlayer(CREATOR_USER_ID + 1)
|
|
86
|
+
|
|
87
|
+
expect(controller.awaitBool(controller.provider:PromiseIsAdmin(player))).toEqual(false)
|
|
88
|
+
|
|
89
|
+
controller:destroy()
|
|
90
|
+
end)
|
|
91
|
+
|
|
92
|
+
it("should deny another user creator permission", function()
|
|
93
|
+
local controller = setup()
|
|
94
|
+
local player = controller.fakePlayer(CREATOR_USER_ID + 1)
|
|
95
|
+
|
|
96
|
+
expect(controller.awaitBool(controller.provider:PromiseIsCreator(player))).toEqual(false)
|
|
97
|
+
|
|
98
|
+
controller:destroy()
|
|
99
|
+
end)
|
|
100
|
+
|
|
101
|
+
it("should resolve synchronously for the IsAdmin wrapper", function()
|
|
102
|
+
local controller = setup()
|
|
103
|
+
local player = controller.fakePlayer(CREATOR_USER_ID)
|
|
104
|
+
|
|
105
|
+
expect(controller.provider:IsAdmin(player)).toEqual(true)
|
|
106
|
+
|
|
107
|
+
controller:destroy()
|
|
108
|
+
end)
|
|
109
|
+
|
|
110
|
+
it("should reject a non-player value", function()
|
|
111
|
+
local controller = setup()
|
|
112
|
+
|
|
113
|
+
expect(function()
|
|
114
|
+
controller.provider:PromiseIsPermissionLevel(nil :: any, PermissionLevel.ADMIN)
|
|
115
|
+
end).toThrow("Bad player")
|
|
116
|
+
|
|
117
|
+
controller:destroy()
|
|
118
|
+
end)
|
|
119
|
+
|
|
120
|
+
it("should reject an invalid permission level", function()
|
|
121
|
+
local controller = setup()
|
|
122
|
+
local player = controller.fakePlayer(CREATOR_USER_ID)
|
|
123
|
+
|
|
124
|
+
expect(function()
|
|
125
|
+
controller.provider:PromiseIsPermissionLevel(player, "not-a-level" :: any)
|
|
126
|
+
end).toThrow()
|
|
127
|
+
|
|
128
|
+
controller:destroy()
|
|
129
|
+
end)
|
|
130
|
+
end)
|
|
@@ -15,6 +15,7 @@ local GroupUtils = require("GroupUtils")
|
|
|
15
15
|
local PermissionLevel = require("PermissionLevel")
|
|
16
16
|
local PermissionProviderConstants = require("PermissionProviderConstants")
|
|
17
17
|
local PermissionProviderUtils = require("PermissionProviderUtils")
|
|
18
|
+
local PlayerMock = require("PlayerMock")
|
|
18
19
|
local Promise = require("Promise")
|
|
19
20
|
|
|
20
21
|
local GroupPermissionProvider = setmetatable({}, BasePermissionProvider)
|
|
@@ -62,7 +63,7 @@ function GroupPermissionProvider.Start(self: GroupPermissionProvider)
|
|
|
62
63
|
getmetatable(GroupPermissionProvider).Start(self)
|
|
63
64
|
|
|
64
65
|
self._maid:GiveTask(Players.PlayerRemoving:Connect(function(player: Player)
|
|
65
|
-
local userId = player.UserId
|
|
66
|
+
local userId = if PlayerMock.isMock(player) then PlayerMock.read(player, "UserId") else player.UserId
|
|
66
67
|
|
|
67
68
|
self._adminsCache[userId] = nil
|
|
68
69
|
self._creatorCache[userId] = nil
|
|
@@ -97,7 +98,7 @@ function GroupPermissionProvider.PromiseIsPermissionLevel(
|
|
|
97
98
|
player: Player,
|
|
98
99
|
permissionLevel: PermissionLevel.PermissionLevel
|
|
99
100
|
): Promise.Promise<boolean>
|
|
100
|
-
assert(typeof(player) == "Instance" and player:IsA("Player"), "Bad player")
|
|
101
|
+
assert((typeof(player) == "Instance" and player:IsA("Player")) or PlayerMock.isMock(player), "Bad player")
|
|
101
102
|
assert(PermissionLevel:IsValue(permissionLevel))
|
|
102
103
|
|
|
103
104
|
if permissionLevel == PermissionLevel.ADMIN then
|
|
@@ -113,10 +114,11 @@ function GroupPermissionProvider._promiseIsCreator(
|
|
|
113
114
|
self: GroupPermissionProvider,
|
|
114
115
|
player: Player
|
|
115
116
|
): Promise.Promise<boolean>
|
|
116
|
-
assert(typeof(player) == "Instance" and player:IsA("Player"), "Bad player")
|
|
117
|
+
assert((typeof(player) == "Instance" and player:IsA("Player")) or PlayerMock.isMock(player), "Bad player")
|
|
117
118
|
assert(player:IsDescendantOf(game), "Bad player")
|
|
118
119
|
|
|
119
|
-
if
|
|
120
|
+
local userId = if PlayerMock.isMock(player) then PlayerMock.read(player, "UserId") else player.UserId
|
|
121
|
+
if self._creatorCache[userId] then
|
|
120
122
|
return Promise.resolved(true)
|
|
121
123
|
end
|
|
122
124
|
|
|
@@ -131,12 +133,13 @@ function GroupPermissionProvider._promiseIsAdmin(
|
|
|
131
133
|
): Promise.Promise<boolean>
|
|
132
134
|
assert(player:IsDescendantOf(game))
|
|
133
135
|
|
|
136
|
+
local userId = if PlayerMock.isMock(player) then PlayerMock.read(player, "UserId") else player.UserId
|
|
134
137
|
-- really not saving much time.
|
|
135
|
-
if self._creatorCache[
|
|
138
|
+
if self._creatorCache[userId] then
|
|
136
139
|
return Promise.resolved(true)
|
|
137
140
|
end
|
|
138
141
|
|
|
139
|
-
if self._adminsCache[
|
|
142
|
+
if self._adminsCache[userId] then
|
|
140
143
|
return Promise.resolved(true)
|
|
141
144
|
end
|
|
142
145
|
|
|
@@ -148,13 +151,14 @@ end
|
|
|
148
151
|
function GroupPermissionProvider._handlePlayer(self: GroupPermissionProvider, player: Player): ()
|
|
149
152
|
assert(player, "Bad player")
|
|
150
153
|
|
|
154
|
+
local userId = if PlayerMock.isMock(player) then PlayerMock.read(player, "UserId") else player.UserId
|
|
151
155
|
self:_promiseRankInGroup(player):Then(function(rank)
|
|
152
156
|
if rank >= self._config.minAdminRequiredRank then
|
|
153
|
-
self._adminsCache[
|
|
157
|
+
self._adminsCache[userId] = true
|
|
154
158
|
end
|
|
155
159
|
|
|
156
160
|
if rank >= self._config.minCreatorRequiredRank then
|
|
157
|
-
self._creatorCache[
|
|
161
|
+
self._creatorCache[userId] = true
|
|
158
162
|
end
|
|
159
163
|
end)
|
|
160
164
|
end
|
|
@@ -165,12 +169,13 @@ function GroupPermissionProvider._promiseRankInGroup(
|
|
|
165
169
|
): Promise.Promise<number>
|
|
166
170
|
assert(typeof(player) == "Instance", "Bad player")
|
|
167
171
|
|
|
168
|
-
if
|
|
169
|
-
|
|
172
|
+
local userId = if PlayerMock.isMock(player) then PlayerMock.read(player, "UserId") else player.UserId
|
|
173
|
+
if self._promiseRankPromisesCache[userId] then
|
|
174
|
+
return self._promiseRankPromisesCache[userId]
|
|
170
175
|
end
|
|
171
176
|
|
|
172
|
-
self._promiseRankPromisesCache[
|
|
173
|
-
return self._promiseRankPromisesCache[
|
|
177
|
+
self._promiseRankPromisesCache[userId] = GroupUtils.promiseRankInGroup(player, self._config.groupId)
|
|
178
|
+
return self._promiseRankPromisesCache[userId]
|
|
174
179
|
end
|
|
175
180
|
|
|
176
181
|
return GroupPermissionProvider
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
--!strict
|
|
2
|
+
--[[
|
|
3
|
+
Coverage for GroupPermissionProvider using PlayerMock players. Group membership is injected
|
|
4
|
+
via GroupTestUtils.assignGroupInfo (defaulting to non-member, rank 0 — a mock is in no real
|
|
5
|
+
group), intercepted at the GroupService engine calls, so permission outcomes flow through
|
|
6
|
+
GroupUtils' real parsing and the provider's real threshold logic deterministically — no group
|
|
7
|
+
API call and no Studio dependence.
|
|
8
|
+
|
|
9
|
+
@class GroupPermissionProvider.spec.lua
|
|
10
|
+
]]
|
|
11
|
+
|
|
12
|
+
local require = require(script.Parent.loader).load(script)
|
|
13
|
+
|
|
14
|
+
local Workspace = game:GetService("Workspace")
|
|
15
|
+
|
|
16
|
+
local GroupPermissionProvider = require("GroupPermissionProvider")
|
|
17
|
+
local GroupTestUtils = require("GroupTestUtils")
|
|
18
|
+
local Jest = require("Jest")
|
|
19
|
+
local Maid = require("Maid")
|
|
20
|
+
local PermissionLevel = require("PermissionLevel")
|
|
21
|
+
local PermissionProviderUtils = require("PermissionProviderUtils")
|
|
22
|
+
local PlayerMock = require("PlayerMock")
|
|
23
|
+
local PromiseTestUtils = require("PromiseTestUtils")
|
|
24
|
+
|
|
25
|
+
local describe = Jest.Globals.describe
|
|
26
|
+
local expect = Jest.Globals.expect
|
|
27
|
+
local it = Jest.Globals.it
|
|
28
|
+
|
|
29
|
+
local remoteNameCounter = 0
|
|
30
|
+
|
|
31
|
+
local function setup(options: { minAdminRequiredRank: number, minCreatorRequiredRank: number })
|
|
32
|
+
local maid = Maid.new()
|
|
33
|
+
|
|
34
|
+
-- Unique remote function name so providers from separate tests never share the global remote.
|
|
35
|
+
remoteNameCounter += 1
|
|
36
|
+
local provider = maid:Add(GroupPermissionProvider.new(PermissionProviderUtils.createGroupRankConfig({
|
|
37
|
+
groupId = 12345,
|
|
38
|
+
minAdminRequiredRank = options.minAdminRequiredRank,
|
|
39
|
+
minCreatorRequiredRank = options.minCreatorRequiredRank,
|
|
40
|
+
remoteFunctionName = string.format("GroupPermissionProviderSpecRemote%d", remoteNameCounter),
|
|
41
|
+
})))
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
provider = provider,
|
|
45
|
+
fakePlayer = function(userId: number): Player
|
|
46
|
+
local player = maid:Add(PlayerMock.new({ UserId = userId }))
|
|
47
|
+
player.Parent = Workspace
|
|
48
|
+
return player
|
|
49
|
+
end,
|
|
50
|
+
awaitBool = function(promise: any): boolean
|
|
51
|
+
expect(PromiseTestUtils.awaitSettled(promise, 5)).toEqual(true)
|
|
52
|
+
local ok, value = promise:Yield()
|
|
53
|
+
expect(ok).toEqual(true)
|
|
54
|
+
return value
|
|
55
|
+
end,
|
|
56
|
+
destroy = function(_self)
|
|
57
|
+
maid:DoCleaning()
|
|
58
|
+
end,
|
|
59
|
+
}
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
describe("GroupPermissionProvider.new", function()
|
|
63
|
+
it("should reject a single user config", function()
|
|
64
|
+
expect(function()
|
|
65
|
+
GroupPermissionProvider.new(PermissionProviderUtils.createSingleUserConfig({
|
|
66
|
+
userId = 12345,
|
|
67
|
+
}) :: any)
|
|
68
|
+
end).toThrow("Bad configType")
|
|
69
|
+
end)
|
|
70
|
+
end)
|
|
71
|
+
|
|
72
|
+
describe("GroupPermissionProvider.PromiseIsPermissionLevel", function()
|
|
73
|
+
it("should grant both levels when the rank thresholds are met", function()
|
|
74
|
+
local controller = setup({ minAdminRequiredRank = 0, minCreatorRequiredRank = 0 })
|
|
75
|
+
local player = controller.fakePlayer(111)
|
|
76
|
+
|
|
77
|
+
expect(controller.awaitBool(controller.provider:PromiseIsAdmin(player))).toEqual(true)
|
|
78
|
+
expect(controller.awaitBool(controller.provider:PromiseIsCreator(player))).toEqual(true)
|
|
79
|
+
|
|
80
|
+
controller:destroy()
|
|
81
|
+
end)
|
|
82
|
+
|
|
83
|
+
it("should deny both levels when the rank thresholds are not met", function()
|
|
84
|
+
local controller = setup({ minAdminRequiredRank = 100, minCreatorRequiredRank = 200 })
|
|
85
|
+
local player = controller.fakePlayer(111)
|
|
86
|
+
|
|
87
|
+
expect(controller.awaitBool(controller.provider:PromiseIsAdmin(player))).toEqual(false)
|
|
88
|
+
expect(controller.awaitBool(controller.provider:PromiseIsCreator(player))).toEqual(false)
|
|
89
|
+
|
|
90
|
+
controller:destroy()
|
|
91
|
+
end)
|
|
92
|
+
|
|
93
|
+
it("should grant admin but deny creator for a tiered config", function()
|
|
94
|
+
local controller = setup({ minAdminRequiredRank = 0, minCreatorRequiredRank = 1 })
|
|
95
|
+
local player = controller.fakePlayer(111)
|
|
96
|
+
|
|
97
|
+
expect(controller.awaitBool(controller.provider:PromiseIsAdmin(player))).toEqual(true)
|
|
98
|
+
expect(controller.awaitBool(controller.provider:PromiseIsCreator(player))).toEqual(false)
|
|
99
|
+
|
|
100
|
+
controller:destroy()
|
|
101
|
+
end)
|
|
102
|
+
|
|
103
|
+
it("should grant admin but deny creator from an injected group rank", function()
|
|
104
|
+
local controller = setup({ minAdminRequiredRank = 100, minCreatorRequiredRank = 200 })
|
|
105
|
+
local player = controller.fakePlayer(111)
|
|
106
|
+
GroupTestUtils.assignGroupInfo(player, 12345, { rank = 150, role = "Admin" })
|
|
107
|
+
|
|
108
|
+
expect(controller.awaitBool(controller.provider:PromiseIsAdmin(player))).toEqual(true)
|
|
109
|
+
expect(controller.awaitBool(controller.provider:PromiseIsCreator(player))).toEqual(false)
|
|
110
|
+
|
|
111
|
+
controller:destroy()
|
|
112
|
+
end)
|
|
113
|
+
|
|
114
|
+
it("should grant both levels from an injected group rank meeting both thresholds", function()
|
|
115
|
+
local controller = setup({ minAdminRequiredRank = 100, minCreatorRequiredRank = 200 })
|
|
116
|
+
local player = controller.fakePlayer(111)
|
|
117
|
+
GroupTestUtils.assignGroupInfo(player, 12345, { rank = 255, role = "Owner" })
|
|
118
|
+
|
|
119
|
+
expect(controller.awaitBool(controller.provider:PromiseIsAdmin(player))).toEqual(true)
|
|
120
|
+
expect(controller.awaitBool(controller.provider:PromiseIsCreator(player))).toEqual(true)
|
|
121
|
+
|
|
122
|
+
controller:destroy()
|
|
123
|
+
end)
|
|
124
|
+
|
|
125
|
+
it("should ignore a rank injected for a different group", function()
|
|
126
|
+
local controller = setup({ minAdminRequiredRank = 100, minCreatorRequiredRank = 200 })
|
|
127
|
+
local player = controller.fakePlayer(111)
|
|
128
|
+
GroupTestUtils.assignGroupInfo(player, 99999, { rank = 255, role = "Owner" })
|
|
129
|
+
|
|
130
|
+
expect(controller.awaitBool(controller.provider:PromiseIsAdmin(player))).toEqual(false)
|
|
131
|
+
|
|
132
|
+
controller:destroy()
|
|
133
|
+
end)
|
|
134
|
+
|
|
135
|
+
it("should reject a player outside the game hierarchy", function()
|
|
136
|
+
local controller = setup({ minAdminRequiredRank = 0, minCreatorRequiredRank = 0 })
|
|
137
|
+
local maid = Maid.new()
|
|
138
|
+
local unparented = maid:Add(PlayerMock.new({ UserId = 111 }))
|
|
139
|
+
|
|
140
|
+
expect(function()
|
|
141
|
+
controller.provider:PromiseIsPermissionLevel(unparented, PermissionLevel.CREATOR)
|
|
142
|
+
end).toThrow("Bad player")
|
|
143
|
+
|
|
144
|
+
maid:DoCleaning()
|
|
145
|
+
controller:destroy()
|
|
146
|
+
end)
|
|
147
|
+
|
|
148
|
+
it("should reject a non-player value", function()
|
|
149
|
+
local controller = setup({ minAdminRequiredRank = 0, minCreatorRequiredRank = 0 })
|
|
150
|
+
|
|
151
|
+
expect(function()
|
|
152
|
+
controller.provider:PromiseIsPermissionLevel(nil :: any, PermissionLevel.ADMIN)
|
|
153
|
+
end).toThrow("Bad player")
|
|
154
|
+
|
|
155
|
+
controller:destroy()
|
|
156
|
+
end)
|
|
157
|
+
|
|
158
|
+
it("should reject an invalid permission level", function()
|
|
159
|
+
local controller = setup({ minAdminRequiredRank = 0, minCreatorRequiredRank = 0 })
|
|
160
|
+
local player = controller.fakePlayer(111)
|
|
161
|
+
|
|
162
|
+
expect(function()
|
|
163
|
+
controller.provider:PromiseIsPermissionLevel(player, "not-a-level" :: any)
|
|
164
|
+
end).toThrow()
|
|
165
|
+
|
|
166
|
+
controller:destroy()
|
|
167
|
+
end)
|
|
168
|
+
end)
|
|
169
|
+
|
|
170
|
+
describe("GroupPermissionProvider.Start", function()
|
|
171
|
+
it("should start and clean up without error", function()
|
|
172
|
+
local controller = setup({ minAdminRequiredRank = 100, minCreatorRequiredRank = 200 })
|
|
173
|
+
|
|
174
|
+
expect(function()
|
|
175
|
+
controller.provider:Start()
|
|
176
|
+
end).never.toThrow()
|
|
177
|
+
|
|
178
|
+
controller:destroy()
|
|
179
|
+
end)
|
|
180
|
+
end)
|