@quenty/permissionprovider 14.43.0 → 14.44.1
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/Client/PermissionServiceClient.lua +5 -1
- package/src/Client/PermissionServiceClient.spec.lua +176 -0
- package/src/Client/Providers/PermissionProviderClient.lua +8 -2
- package/src/Client/Providers/PermissionProviderClient.spec.lua +186 -0
- 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.1](https://github.com/Quenty/NevermoreEngine/compare/@quenty/permissionprovider@14.44.0...@quenty/permissionprovider@14.44.1) (2026-07-23)
|
|
7
|
+
|
|
8
|
+
**Note:** Version bump only for package @quenty/permissionprovider
|
|
9
|
+
|
|
10
|
+
# [14.44.0](https://github.com/Quenty/NevermoreEngine/compare/@quenty/permissionprovider@14.43.0...@quenty/permissionprovider@14.44.0) (2026-07-23)
|
|
11
|
+
|
|
12
|
+
### Features
|
|
13
|
+
|
|
14
|
+
- Add baseline player-mock and support across Nevermore for mocked players. ([567d121](https://github.com/Quenty/NevermoreEngine/commit/567d121ffc014b42391554088189a1a6296dda83))
|
|
15
|
+
|
|
6
16
|
# [14.43.0](https://github.com/Quenty/NevermoreEngine/compare/@quenty/permissionprovider@14.42.0...@quenty/permissionprovider@14.43.0) (2026-07-22)
|
|
7
17
|
|
|
8
18
|
**Note:** Version bump only for package @quenty/permissionprovider
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quenty/permissionprovider",
|
|
3
|
-
"version": "14.
|
|
3
|
+
"version": "14.44.1",
|
|
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.1",
|
|
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.1",
|
|
39
|
+
"@quenty/playerutils": "8.36.1",
|
|
40
|
+
"@quenty/promise": "10.21.0",
|
|
41
|
+
"@quenty/remoting": "12.36.1",
|
|
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": "5ea0583dedebba6492f9ac6d719d32b45c5543fa"
|
|
50
51
|
}
|
|
@@ -15,6 +15,7 @@ local require = require(script.Parent.loader).load(script)
|
|
|
15
15
|
local Maid = require("Maid")
|
|
16
16
|
local PermissionProviderClient = require("PermissionProviderClient")
|
|
17
17
|
local PermissionProviderConstants = require("PermissionProviderConstants")
|
|
18
|
+
local PlayerMock = require("PlayerMock")
|
|
18
19
|
local Promise = require("Promise")
|
|
19
20
|
local ServiceBag = require("ServiceBag")
|
|
20
21
|
|
|
@@ -53,7 +54,10 @@ function PermissionServiceClient.PromiseIsAdmin(
|
|
|
53
54
|
self: PermissionServiceClient,
|
|
54
55
|
player: Player?
|
|
55
56
|
): Promise.Promise<boolean>
|
|
56
|
-
assert(
|
|
57
|
+
assert(
|
|
58
|
+
(typeof(player) == "Instance" and (player:IsA("Player") or PlayerMock.isMock(player))) or player == nil,
|
|
59
|
+
"Bad player"
|
|
60
|
+
)
|
|
57
61
|
|
|
58
62
|
return self:PromisePermissionProvider():Then(function(permissionProvider)
|
|
59
63
|
return permissionProvider:PromiseIsAdmin(player)
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
--!strict
|
|
2
|
+
--[[
|
|
3
|
+
Coverage for PermissionServiceClient booted headless in a client ServiceBag against a
|
|
4
|
+
pre-designated mock local player (designation precedes Init -- production parity, where
|
|
5
|
+
Players.LocalPlayer exists before any service runs). Real remotes can never reach a server
|
|
6
|
+
realm headless, so server answers are driven by stubbing the remote promise cached by the
|
|
7
|
+
resolved PermissionProviderClient. The deny case assumes RunService:IsStudio() == false (true
|
|
8
|
+
in the cloud test runner); in Studio the provider short-circuits every answer to true.
|
|
9
|
+
|
|
10
|
+
Bad-player failures surface as synchronous throws, not rejections: the provider promise is
|
|
11
|
+
already fulfilled, so the Then handler (and the provider's asserts) run inline at call time.
|
|
12
|
+
|
|
13
|
+
@class PermissionServiceClient.spec.lua
|
|
14
|
+
]]
|
|
15
|
+
|
|
16
|
+
local require = require(script.Parent.loader).load(script)
|
|
17
|
+
|
|
18
|
+
local Workspace = game:GetService("Workspace")
|
|
19
|
+
|
|
20
|
+
local Jest = require("Jest")
|
|
21
|
+
local Maid = require("Maid")
|
|
22
|
+
local PermissionServiceClient = require("PermissionServiceClient")
|
|
23
|
+
local PlayerMock = require("PlayerMock")
|
|
24
|
+
local Promise = require("Promise")
|
|
25
|
+
local PromiseTestUtils = require("PromiseTestUtils")
|
|
26
|
+
local ServiceBag = require("ServiceBag")
|
|
27
|
+
|
|
28
|
+
local describe = Jest.Globals.describe
|
|
29
|
+
local expect = Jest.Globals.expect
|
|
30
|
+
local it = Jest.Globals.it
|
|
31
|
+
|
|
32
|
+
local LOCAL_USER_ID = 88772001
|
|
33
|
+
|
|
34
|
+
local function setup()
|
|
35
|
+
local maid = Maid.new()
|
|
36
|
+
|
|
37
|
+
local localPlayer = maid:Add(PlayerMock.new({ UserId = LOCAL_USER_ID }))
|
|
38
|
+
localPlayer.Parent = Workspace
|
|
39
|
+
PlayerMock.setMockedLocalPlayer(localPlayer)
|
|
40
|
+
|
|
41
|
+
local serviceBag = maid:Add(ServiceBag.new())
|
|
42
|
+
local service: PermissionServiceClient.PermissionServiceClient =
|
|
43
|
+
serviceBag:GetService(PermissionServiceClient) :: any
|
|
44
|
+
serviceBag:Init()
|
|
45
|
+
serviceBag:Start()
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
localPlayer = localPlayer,
|
|
49
|
+
serviceBag = serviceBag,
|
|
50
|
+
service = service,
|
|
51
|
+
stubServerAnswer = function(invokeServer: () -> any)
|
|
52
|
+
local ok, provider = service:PromisePermissionProvider():Yield()
|
|
53
|
+
assert(ok, "No provider resolved")
|
|
54
|
+
|
|
55
|
+
local anyProvider: any = provider
|
|
56
|
+
anyProvider._remoteFunctionPromise = Promise.resolved({
|
|
57
|
+
InvokeServer = function(_self)
|
|
58
|
+
return invokeServer()
|
|
59
|
+
end,
|
|
60
|
+
})
|
|
61
|
+
end,
|
|
62
|
+
fakePlayer = function(userId: number): Player
|
|
63
|
+
return maid:Add(PlayerMock.new({ UserId = userId }))
|
|
64
|
+
end,
|
|
65
|
+
awaitBool = function(promise: any): boolean
|
|
66
|
+
expect(PromiseTestUtils.awaitSettled(promise, 5)).toEqual(true)
|
|
67
|
+
local ok, value = promise:Yield()
|
|
68
|
+
expect(ok).toEqual(true)
|
|
69
|
+
return value
|
|
70
|
+
end,
|
|
71
|
+
destroy = function(_self)
|
|
72
|
+
PlayerMock.setMockedLocalPlayer(nil)
|
|
73
|
+
maid:DoCleaning()
|
|
74
|
+
end,
|
|
75
|
+
}
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
describe("PermissionServiceClient initialization", function()
|
|
79
|
+
it("resolves the permission provider immediately after init", function()
|
|
80
|
+
local controller = setup()
|
|
81
|
+
|
|
82
|
+
local promise = controller.service:PromisePermissionProvider()
|
|
83
|
+
expect(promise:IsPending()).toEqual(false)
|
|
84
|
+
|
|
85
|
+
local ok, provider = promise:Yield()
|
|
86
|
+
expect(ok).toEqual(true)
|
|
87
|
+
expect((provider :: any).ClassName).toEqual("PermissionProviderClient")
|
|
88
|
+
|
|
89
|
+
controller:destroy()
|
|
90
|
+
end)
|
|
91
|
+
|
|
92
|
+
it("rejects double initialization", function()
|
|
93
|
+
local controller = setup()
|
|
94
|
+
|
|
95
|
+
local bare: any = setmetatable({}, { __index = PermissionServiceClient })
|
|
96
|
+
bare:Init(controller.serviceBag)
|
|
97
|
+
|
|
98
|
+
expect(function()
|
|
99
|
+
bare:Init(controller.serviceBag)
|
|
100
|
+
end).toThrow("Already initialized")
|
|
101
|
+
|
|
102
|
+
bare:Destroy()
|
|
103
|
+
controller:destroy()
|
|
104
|
+
end)
|
|
105
|
+
end)
|
|
106
|
+
|
|
107
|
+
describe("PermissionServiceClient.PromiseIsAdmin", function()
|
|
108
|
+
it("rejects a non-player value", function()
|
|
109
|
+
local controller = setup()
|
|
110
|
+
|
|
111
|
+
expect(function()
|
|
112
|
+
controller.service:PromiseIsAdmin(5 :: any)
|
|
113
|
+
end).toThrow("Bad player")
|
|
114
|
+
|
|
115
|
+
controller:destroy()
|
|
116
|
+
end)
|
|
117
|
+
|
|
118
|
+
it("resolves the server's answer for the designated local player", function()
|
|
119
|
+
local controller = setup()
|
|
120
|
+
controller.stubServerAnswer(function()
|
|
121
|
+
return true
|
|
122
|
+
end)
|
|
123
|
+
|
|
124
|
+
expect(controller.awaitBool(controller.service:PromiseIsAdmin(controller.localPlayer))).toEqual(true)
|
|
125
|
+
|
|
126
|
+
controller:destroy()
|
|
127
|
+
end)
|
|
128
|
+
|
|
129
|
+
it("resolves the server's answer for a nil player", function()
|
|
130
|
+
local controller = setup()
|
|
131
|
+
controller.stubServerAnswer(function()
|
|
132
|
+
return true
|
|
133
|
+
end)
|
|
134
|
+
|
|
135
|
+
expect(controller.awaitBool(controller.service:PromiseIsAdmin(nil))).toEqual(true)
|
|
136
|
+
|
|
137
|
+
controller:destroy()
|
|
138
|
+
end)
|
|
139
|
+
|
|
140
|
+
it("denies the designated local player when the server answers false", function()
|
|
141
|
+
local controller = setup()
|
|
142
|
+
controller.stubServerAnswer(function()
|
|
143
|
+
return false
|
|
144
|
+
end)
|
|
145
|
+
|
|
146
|
+
expect(controller.awaitBool(controller.service:PromiseIsAdmin(controller.localPlayer))).toEqual(false)
|
|
147
|
+
|
|
148
|
+
controller:destroy()
|
|
149
|
+
end)
|
|
150
|
+
|
|
151
|
+
it("throws for a mock that is not the designated local player", function()
|
|
152
|
+
local controller = setup()
|
|
153
|
+
local otherPlayer = controller.fakePlayer(LOCAL_USER_ID + 1)
|
|
154
|
+
|
|
155
|
+
expect(function()
|
|
156
|
+
controller.service:PromiseIsAdmin(otherPlayer)
|
|
157
|
+
end).toThrow("We only support local player")
|
|
158
|
+
|
|
159
|
+
controller:destroy()
|
|
160
|
+
end)
|
|
161
|
+
|
|
162
|
+
it("caches the server answer across calls", function()
|
|
163
|
+
local controller = setup()
|
|
164
|
+
local invokeCount = 0
|
|
165
|
+
controller.stubServerAnswer(function()
|
|
166
|
+
invokeCount += 1
|
|
167
|
+
return true
|
|
168
|
+
end)
|
|
169
|
+
|
|
170
|
+
expect(controller.awaitBool(controller.service:PromiseIsAdmin(controller.localPlayer))).toEqual(true)
|
|
171
|
+
expect(controller.awaitBool(controller.service:PromiseIsAdmin(controller.localPlayer))).toEqual(true)
|
|
172
|
+
expect(invokeCount).toEqual(1)
|
|
173
|
+
|
|
174
|
+
controller:destroy()
|
|
175
|
+
end)
|
|
176
|
+
end)
|
|
@@ -12,6 +12,7 @@ local Players = game:GetService("Players")
|
|
|
12
12
|
local RunService = game:GetService("RunService")
|
|
13
13
|
|
|
14
14
|
local PermissionProviderConstants = require("PermissionProviderConstants")
|
|
15
|
+
local PlayerMock = require("PlayerMock")
|
|
15
16
|
local Promise = require("Promise")
|
|
16
17
|
local PromiseGetRemoteFunction = require("PromiseGetRemoteFunction")
|
|
17
18
|
|
|
@@ -46,10 +47,15 @@ function PermissionProviderClient.PromiseIsAdmin(
|
|
|
46
47
|
self: PermissionProviderClient,
|
|
47
48
|
player: Player?
|
|
48
49
|
): Promise.Promise<boolean>
|
|
49
|
-
assert(
|
|
50
|
+
assert(
|
|
51
|
+
typeof(player) == "Instance" and (player:IsA("Player") or PlayerMock.isMock(player)) or player == nil,
|
|
52
|
+
"Bad player"
|
|
53
|
+
)
|
|
50
54
|
|
|
51
55
|
if player ~= nil then
|
|
52
|
-
|
|
56
|
+
-- Headless (test) sessions have no Players.LocalPlayer; the designated PlayerMock stands in.
|
|
57
|
+
local localPlayer = Players.LocalPlayer or PlayerMock.getMockedLocalPlayer()
|
|
58
|
+
assert(player == localPlayer, "We only support local player for now")
|
|
53
59
|
end
|
|
54
60
|
|
|
55
61
|
if player == nil then
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
--!strict
|
|
2
|
+
--[[
|
|
3
|
+
Coverage for PermissionProviderClient against a stubbed server remote. Headless sessions
|
|
4
|
+
(RunService:IsRunning() == false) hand out detached mock RemoteFunctions that can never reach a
|
|
5
|
+
server realm, so tests inject the remote promise the provider caches and answer InvokeServer
|
|
6
|
+
directly. The deny case assumes RunService:IsStudio() == false (true in the cloud test runner);
|
|
7
|
+
in Studio the provider short-circuits every answer to true.
|
|
8
|
+
|
|
9
|
+
@class PermissionProviderClient.spec.lua
|
|
10
|
+
]]
|
|
11
|
+
|
|
12
|
+
local require = require(script.Parent.loader).load(script)
|
|
13
|
+
|
|
14
|
+
local Workspace = game:GetService("Workspace")
|
|
15
|
+
|
|
16
|
+
local Jest = require("Jest")
|
|
17
|
+
local Maid = require("Maid")
|
|
18
|
+
local PermissionProviderClient = require("PermissionProviderClient")
|
|
19
|
+
local PermissionProviderConstants = require("PermissionProviderConstants")
|
|
20
|
+
local PlayerMock = require("PlayerMock")
|
|
21
|
+
local Promise = require("Promise")
|
|
22
|
+
local PromiseTestUtils = require("PromiseTestUtils")
|
|
23
|
+
|
|
24
|
+
local describe = Jest.Globals.describe
|
|
25
|
+
local expect = Jest.Globals.expect
|
|
26
|
+
local it = Jest.Globals.it
|
|
27
|
+
|
|
28
|
+
local LOCAL_USER_ID = 88771001
|
|
29
|
+
|
|
30
|
+
local function setup()
|
|
31
|
+
local maid = Maid.new()
|
|
32
|
+
|
|
33
|
+
local localPlayer = maid:Add(PlayerMock.new({ UserId = LOCAL_USER_ID }))
|
|
34
|
+
localPlayer.Parent = Workspace
|
|
35
|
+
PlayerMock.setMockedLocalPlayer(localPlayer)
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
localPlayer = localPlayer,
|
|
39
|
+
makeProvider = function(invokeServer: (() -> any)?): any
|
|
40
|
+
local provider: any = PermissionProviderClient.new("PermissionProviderClientSpecRemote")
|
|
41
|
+
if invokeServer then
|
|
42
|
+
provider._remoteFunctionPromise = Promise.resolved({
|
|
43
|
+
InvokeServer = function(_self)
|
|
44
|
+
return invokeServer()
|
|
45
|
+
end,
|
|
46
|
+
})
|
|
47
|
+
end
|
|
48
|
+
return provider
|
|
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
|
+
awaitError = function(promise: any): string
|
|
60
|
+
expect(PromiseTestUtils.awaitSettled(promise, 5)).toEqual(true)
|
|
61
|
+
local ok, err = promise:Yield()
|
|
62
|
+
expect(ok).toEqual(false)
|
|
63
|
+
return tostring(err)
|
|
64
|
+
end,
|
|
65
|
+
destroy = function(_self)
|
|
66
|
+
PlayerMock.setMockedLocalPlayer(nil)
|
|
67
|
+
maid:DoCleaning()
|
|
68
|
+
end,
|
|
69
|
+
}
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
describe("PermissionProviderClient construction", function()
|
|
73
|
+
it("defaults the remote function name when none is given", function()
|
|
74
|
+
local provider: any = PermissionProviderClient.new(nil :: any)
|
|
75
|
+
|
|
76
|
+
expect(provider._remoteFunctionName).toEqual(PermissionProviderConstants.DEFAULT_REMOTE_FUNCTION_NAME)
|
|
77
|
+
end)
|
|
78
|
+
|
|
79
|
+
it("uses the provided remote function name", function()
|
|
80
|
+
local provider: any = PermissionProviderClient.new("PermissionProviderClientSpecRemote")
|
|
81
|
+
|
|
82
|
+
expect(provider._remoteFunctionName).toEqual("PermissionProviderClientSpecRemote")
|
|
83
|
+
end)
|
|
84
|
+
end)
|
|
85
|
+
|
|
86
|
+
describe("PermissionProviderClient.PromiseIsAdmin argument validation", function()
|
|
87
|
+
it("rejects a non-player value", function()
|
|
88
|
+
local controller = setup()
|
|
89
|
+
local provider = controller.makeProvider()
|
|
90
|
+
|
|
91
|
+
expect(function()
|
|
92
|
+
provider:PromiseIsAdmin(5 :: any)
|
|
93
|
+
end).toThrow("Bad player")
|
|
94
|
+
|
|
95
|
+
controller:destroy()
|
|
96
|
+
end)
|
|
97
|
+
|
|
98
|
+
it("rejects a player other than the designated local player", function()
|
|
99
|
+
local controller = setup()
|
|
100
|
+
local provider = controller.makeProvider()
|
|
101
|
+
local otherPlayer = controller.fakePlayer(LOCAL_USER_ID + 1)
|
|
102
|
+
|
|
103
|
+
expect(function()
|
|
104
|
+
provider:PromiseIsAdmin(otherPlayer)
|
|
105
|
+
end).toThrow("We only support local player")
|
|
106
|
+
|
|
107
|
+
controller:destroy()
|
|
108
|
+
end)
|
|
109
|
+
end)
|
|
110
|
+
|
|
111
|
+
describe("PermissionProviderClient.PromiseIsAdmin", function()
|
|
112
|
+
it("resolves the server's answer for the designated local player", function()
|
|
113
|
+
local controller = setup()
|
|
114
|
+
local provider = controller.makeProvider(function()
|
|
115
|
+
return true
|
|
116
|
+
end)
|
|
117
|
+
|
|
118
|
+
expect(controller.awaitBool(provider:PromiseIsAdmin(controller.localPlayer))).toEqual(true)
|
|
119
|
+
|
|
120
|
+
controller:destroy()
|
|
121
|
+
end)
|
|
122
|
+
|
|
123
|
+
it("resolves the server's answer for a nil player", function()
|
|
124
|
+
local controller = setup()
|
|
125
|
+
local provider = controller.makeProvider(function()
|
|
126
|
+
return true
|
|
127
|
+
end)
|
|
128
|
+
|
|
129
|
+
expect(controller.awaitBool(provider:PromiseIsAdmin(nil))).toEqual(true)
|
|
130
|
+
|
|
131
|
+
controller:destroy()
|
|
132
|
+
end)
|
|
133
|
+
|
|
134
|
+
it("denies when the server answers false", function()
|
|
135
|
+
local controller = setup()
|
|
136
|
+
local provider = controller.makeProvider(function()
|
|
137
|
+
return false
|
|
138
|
+
end)
|
|
139
|
+
|
|
140
|
+
expect(controller.awaitBool(provider:PromiseIsAdmin(controller.localPlayer))).toEqual(false)
|
|
141
|
+
|
|
142
|
+
controller:destroy()
|
|
143
|
+
end)
|
|
144
|
+
|
|
145
|
+
it("rejects a non-boolean server answer", function()
|
|
146
|
+
local controller = setup()
|
|
147
|
+
local provider = controller.makeProvider(function()
|
|
148
|
+
return "yes"
|
|
149
|
+
end)
|
|
150
|
+
|
|
151
|
+
local message = controller.awaitError(provider:PromiseIsAdmin(controller.localPlayer))
|
|
152
|
+
expect(message).toContain("Got non-boolean from server")
|
|
153
|
+
|
|
154
|
+
controller:destroy()
|
|
155
|
+
end)
|
|
156
|
+
|
|
157
|
+
it("rejects when the server invoke throws", function()
|
|
158
|
+
local controller = setup()
|
|
159
|
+
local provider = controller.makeProvider(function()
|
|
160
|
+
error("Server invoke failed")
|
|
161
|
+
end)
|
|
162
|
+
|
|
163
|
+
local message = controller.awaitError(provider:PromiseIsAdmin(controller.localPlayer))
|
|
164
|
+
expect(message).toContain("Server invoke failed")
|
|
165
|
+
|
|
166
|
+
controller:destroy()
|
|
167
|
+
end)
|
|
168
|
+
|
|
169
|
+
it("caches the admin answer across calls", function()
|
|
170
|
+
local controller = setup()
|
|
171
|
+
local invokeCount = 0
|
|
172
|
+
local provider = controller.makeProvider(function()
|
|
173
|
+
invokeCount += 1
|
|
174
|
+
return true
|
|
175
|
+
end)
|
|
176
|
+
|
|
177
|
+
local first = provider:PromiseIsAdmin(controller.localPlayer)
|
|
178
|
+
local second = provider:PromiseIsAdmin(controller.localPlayer)
|
|
179
|
+
|
|
180
|
+
expect(second).toBe(first)
|
|
181
|
+
expect(controller.awaitBool(first)).toEqual(true)
|
|
182
|
+
expect(invokeCount).toEqual(1)
|
|
183
|
+
|
|
184
|
+
controller:destroy()
|
|
185
|
+
end)
|
|
186
|
+
end)
|
|
@@ -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
|