@quenty/marketplaceutils 11.24.0 → 11.25.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/deploy.nevermore.json +10 -0
- package/package.json +7 -4
- package/src/Shared/MarketplaceUtils.lua +29 -6
- package/src/Shared/MarketplaceUtils.spec.lua +215 -0
- package/src/jest.config.lua +3 -0
- package/test/default.project.json +25 -0
- package/test/scripts/Server/ServerMain.server.lua +13 -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
|
+
## [11.25.1](https://github.com/Quenty/NevermoreEngine/compare/@quenty/marketplaceutils@11.25.0...@quenty/marketplaceutils@11.25.1) (2026-07-23)
|
|
7
|
+
|
|
8
|
+
**Note:** Version bump only for package @quenty/marketplaceutils
|
|
9
|
+
|
|
10
|
+
# [11.25.0](https://github.com/Quenty/NevermoreEngine/compare/@quenty/marketplaceutils@11.24.0...@quenty/marketplaceutils@11.25.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
|
# [11.24.0](https://github.com/Quenty/NevermoreEngine/compare/@quenty/marketplaceutils@11.23.0...@quenty/marketplaceutils@11.24.0) (2026-07-20)
|
|
7
17
|
|
|
8
18
|
### Features
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quenty/marketplaceutils",
|
|
3
|
-
"version": "11.
|
|
3
|
+
"version": "11.25.1",
|
|
4
4
|
"description": "Provides utility methods for MarketplaceService",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"Roblox",
|
|
@@ -31,11 +31,14 @@
|
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"@quenty/loader": "10.11.0",
|
|
33
33
|
"@quenty/memoize": "1.10.0",
|
|
34
|
-
"@quenty/
|
|
35
|
-
"@quenty/
|
|
34
|
+
"@quenty/nevermore-test-runner": "1.5.0",
|
|
35
|
+
"@quenty/playermock": "1.1.1",
|
|
36
|
+
"@quenty/promise": "10.21.0",
|
|
37
|
+
"@quenty/servicebag": "11.20.0",
|
|
38
|
+
"@quentystudios/jest-lua": "3.10.0-quenty.2"
|
|
36
39
|
},
|
|
37
40
|
"publishConfig": {
|
|
38
41
|
"access": "public"
|
|
39
42
|
},
|
|
40
|
-
"gitHead": "
|
|
43
|
+
"gitHead": "5ea0583dedebba6492f9ac6d719d32b45c5543fa"
|
|
41
44
|
}
|
|
@@ -8,6 +8,7 @@ local require = require(script.Parent.loader).load(script)
|
|
|
8
8
|
|
|
9
9
|
local MarketplaceService = game:GetService("MarketplaceService")
|
|
10
10
|
|
|
11
|
+
local PlayerMock = require("PlayerMock")
|
|
11
12
|
local Promise = require("Promise")
|
|
12
13
|
|
|
13
14
|
local MarketplaceUtils = {}
|
|
@@ -153,13 +154,18 @@ function MarketplaceUtils.promiseUserSubscriptionStatus(
|
|
|
153
154
|
player: Player,
|
|
154
155
|
subscriptionId: string
|
|
155
156
|
): Promise.Promise<UserSubscriptonStatus>
|
|
156
|
-
assert(typeof(player) == "Instance" and player:IsA("Player"), "Bad player")
|
|
157
|
+
assert(typeof(player) == "Instance" and (player:IsA("Player") or PlayerMock.isMock(player)), "Bad player")
|
|
157
158
|
assert(type(subscriptionId) == "string", "Bad subscriptionId")
|
|
158
159
|
|
|
159
160
|
return Promise.spawn(function(resolve, reject)
|
|
160
161
|
local subStatus
|
|
161
162
|
local ok, err = pcall(function()
|
|
162
|
-
|
|
163
|
+
if PlayerMock.isMock(player) then
|
|
164
|
+
subStatus =
|
|
165
|
+
PlayerMock.readLookup(player, "MarketplaceService.GetUserSubscriptionStatusAsync", subscriptionId)
|
|
166
|
+
else
|
|
167
|
+
subStatus = MarketplaceService:GetUserSubscriptionStatusAsync(player, subscriptionId)
|
|
168
|
+
end
|
|
163
169
|
end)
|
|
164
170
|
if not ok then
|
|
165
171
|
return reject(err)
|
|
@@ -184,7 +190,12 @@ function MarketplaceUtils.promiseUserOwnsGamePass(userId: number, gamePassId: nu
|
|
|
184
190
|
return Promise.spawn(function(resolve, reject)
|
|
185
191
|
local result
|
|
186
192
|
local ok, err = pcall(function()
|
|
187
|
-
|
|
193
|
+
local mockPlayer = PlayerMock.getMockByUserId(userId)
|
|
194
|
+
if mockPlayer ~= nil then
|
|
195
|
+
result = PlayerMock.readLookup(mockPlayer, "MarketplaceService.UserOwnsGamePassAsync", gamePassId)
|
|
196
|
+
else
|
|
197
|
+
result = MarketplaceService:UserOwnsGamePassAsync(userId, gamePassId)
|
|
198
|
+
end
|
|
188
199
|
end)
|
|
189
200
|
if not ok then
|
|
190
201
|
return reject(err)
|
|
@@ -209,7 +220,11 @@ function MarketplaceUtils.promisePlayerOwnsAsset(player: Player, assetId: number
|
|
|
209
220
|
return Promise.spawn(function(resolve, reject)
|
|
210
221
|
local result
|
|
211
222
|
local ok, err = pcall(function()
|
|
212
|
-
|
|
223
|
+
if PlayerMock.isMock(player) then
|
|
224
|
+
result = PlayerMock.readLookup(player, "MarketplaceService.PlayerOwnsAsset", assetId)
|
|
225
|
+
else
|
|
226
|
+
result = MarketplaceService:PlayerOwnsAsset(player, assetId)
|
|
227
|
+
end
|
|
213
228
|
end)
|
|
214
229
|
if not ok then
|
|
215
230
|
return reject(err)
|
|
@@ -238,7 +253,11 @@ function MarketplaceUtils.promisePlayerOwnsAssetAsync(player: Player, assetId: n
|
|
|
238
253
|
return Promise.spawn(function(resolve, reject)
|
|
239
254
|
local result
|
|
240
255
|
local ok, err = pcall(function()
|
|
241
|
-
|
|
256
|
+
if PlayerMock.isMock(player) then
|
|
257
|
+
result = PlayerMock.readLookup(player, "MarketplaceService.PlayerOwnsAssetAsync", assetId)
|
|
258
|
+
else
|
|
259
|
+
result = MarketplaceService:PlayerOwnsAssetAsync(player, assetId)
|
|
260
|
+
end
|
|
242
261
|
end)
|
|
243
262
|
if not ok then
|
|
244
263
|
return reject(err)
|
|
@@ -263,7 +282,11 @@ function MarketplaceUtils.promisePlayerOwnsBundle(player: Player, bundleId: numb
|
|
|
263
282
|
return Promise.spawn(function(resolve, reject)
|
|
264
283
|
local result
|
|
265
284
|
local ok, err = pcall(function()
|
|
266
|
-
|
|
285
|
+
if PlayerMock.isMock(player) then
|
|
286
|
+
result = PlayerMock.readLookup(player, "MarketplaceService.PlayerOwnsBundle", bundleId)
|
|
287
|
+
else
|
|
288
|
+
result = MarketplaceService:PlayerOwnsBundle(player, bundleId)
|
|
289
|
+
end
|
|
267
290
|
end)
|
|
268
291
|
if not ok then
|
|
269
292
|
return reject(err)
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
--!strict
|
|
2
|
+
--[[
|
|
3
|
+
@class MarketplaceUtils.spec.lua
|
|
4
|
+
]]
|
|
5
|
+
|
|
6
|
+
local require = require(script.Parent.loader).load(script)
|
|
7
|
+
|
|
8
|
+
local Workspace = game:GetService("Workspace")
|
|
9
|
+
|
|
10
|
+
local Jest = require("Jest")
|
|
11
|
+
local MarketplaceServiceCache = require("MarketplaceServiceCache")
|
|
12
|
+
local MarketplaceUtils = require("MarketplaceUtils")
|
|
13
|
+
local PlayerMock = require("PlayerMock")
|
|
14
|
+
local PromiseTestUtils = require("PromiseTestUtils")
|
|
15
|
+
|
|
16
|
+
local afterEach = Jest.Globals.afterEach
|
|
17
|
+
local describe = Jest.Globals.describe
|
|
18
|
+
local expect = Jest.Globals.expect
|
|
19
|
+
local it = Jest.Globals.it
|
|
20
|
+
|
|
21
|
+
local MOCK_USER_ID = 88771001
|
|
22
|
+
local GAME_PASS_ID = 111222333
|
|
23
|
+
local ASSET_ID = 444555666
|
|
24
|
+
local BUNDLE_ID = 777888999
|
|
25
|
+
local SUBSCRIPTION_ID = "EXP-88771001"
|
|
26
|
+
|
|
27
|
+
local createdMocks: { Player } = {}
|
|
28
|
+
|
|
29
|
+
local function makeMock(overrides: { [string]: any }?): Player
|
|
30
|
+
local player = PlayerMock.new(overrides)
|
|
31
|
+
player.Parent = Workspace
|
|
32
|
+
table.insert(createdMocks, player)
|
|
33
|
+
return player
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
local function awaitBool(promise: any): boolean
|
|
37
|
+
expect(PromiseTestUtils.awaitSettled(promise, 5)).toBe(true)
|
|
38
|
+
local ok, value = promise:Yield()
|
|
39
|
+
expect(ok).toBe(true)
|
|
40
|
+
return value
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
afterEach(function()
|
|
44
|
+
for _, player in createdMocks do
|
|
45
|
+
player:Destroy()
|
|
46
|
+
end
|
|
47
|
+
table.clear(createdMocks)
|
|
48
|
+
end)
|
|
49
|
+
|
|
50
|
+
describe("MarketplaceUtils.promiseUserOwnsGamePass", function()
|
|
51
|
+
it("resolves false for a mock's userId with no injected ownership", function()
|
|
52
|
+
makeMock({ UserId = MOCK_USER_ID })
|
|
53
|
+
|
|
54
|
+
expect(awaitBool(MarketplaceUtils.promiseUserOwnsGamePass(MOCK_USER_ID, GAME_PASS_ID))).toBe(false)
|
|
55
|
+
end)
|
|
56
|
+
|
|
57
|
+
it("resolves the ownership injected on the mock", function()
|
|
58
|
+
local player = makeMock({ UserId = MOCK_USER_ID })
|
|
59
|
+
PlayerMock.writeLookup(player, "MarketplaceService.UserOwnsGamePassAsync", GAME_PASS_ID, true)
|
|
60
|
+
|
|
61
|
+
expect(awaitBool(MarketplaceUtils.promiseUserOwnsGamePass(MOCK_USER_ID, GAME_PASS_ID))).toBe(true)
|
|
62
|
+
end)
|
|
63
|
+
|
|
64
|
+
it("keys the injected ownership by gamePassId", function()
|
|
65
|
+
local player = makeMock({ UserId = MOCK_USER_ID })
|
|
66
|
+
PlayerMock.writeLookup(player, "MarketplaceService.UserOwnsGamePassAsync", GAME_PASS_ID, true)
|
|
67
|
+
|
|
68
|
+
expect(awaitBool(MarketplaceUtils.promiseUserOwnsGamePass(MOCK_USER_ID, GAME_PASS_ID + 1))).toBe(false)
|
|
69
|
+
end)
|
|
70
|
+
|
|
71
|
+
it("clears back to false when the injection is cleared", function()
|
|
72
|
+
local player = makeMock({ UserId = MOCK_USER_ID })
|
|
73
|
+
PlayerMock.writeLookup(player, "MarketplaceService.UserOwnsGamePassAsync", GAME_PASS_ID, true)
|
|
74
|
+
PlayerMock.writeLookup(player, "MarketplaceService.UserOwnsGamePassAsync", GAME_PASS_ID, nil)
|
|
75
|
+
|
|
76
|
+
expect(awaitBool(MarketplaceUtils.promiseUserOwnsGamePass(MOCK_USER_ID, GAME_PASS_ID))).toBe(false)
|
|
77
|
+
end)
|
|
78
|
+
|
|
79
|
+
it("asserts on a non-number userId", function()
|
|
80
|
+
expect(function()
|
|
81
|
+
MarketplaceUtils.promiseUserOwnsGamePass("nope" :: any, GAME_PASS_ID)
|
|
82
|
+
end).toThrow()
|
|
83
|
+
end)
|
|
84
|
+
end)
|
|
85
|
+
|
|
86
|
+
describe("MarketplaceUtils.promisePlayerOwnsAsset", function()
|
|
87
|
+
it("resolves false for a mock with no injected ownership", function()
|
|
88
|
+
local player = makeMock({ UserId = MOCK_USER_ID })
|
|
89
|
+
|
|
90
|
+
expect(awaitBool(MarketplaceUtils.promisePlayerOwnsAsset(player, ASSET_ID))).toBe(false)
|
|
91
|
+
end)
|
|
92
|
+
|
|
93
|
+
it("resolves the ownership injected on the mock", function()
|
|
94
|
+
local player = makeMock({ UserId = MOCK_USER_ID })
|
|
95
|
+
PlayerMock.writeLookup(player, "MarketplaceService.PlayerOwnsAsset", ASSET_ID, true)
|
|
96
|
+
|
|
97
|
+
expect(awaitBool(MarketplaceUtils.promisePlayerOwnsAsset(player, ASSET_ID))).toBe(true)
|
|
98
|
+
end)
|
|
99
|
+
end)
|
|
100
|
+
|
|
101
|
+
describe("MarketplaceUtils.promisePlayerOwnsAssetAsync", function()
|
|
102
|
+
it("resolves false for a mock with no injected ownership", function()
|
|
103
|
+
local player = makeMock({ UserId = MOCK_USER_ID })
|
|
104
|
+
|
|
105
|
+
expect(awaitBool(MarketplaceUtils.promisePlayerOwnsAssetAsync(player, ASSET_ID))).toBe(false)
|
|
106
|
+
end)
|
|
107
|
+
|
|
108
|
+
it("resolves the ownership injected on the mock", function()
|
|
109
|
+
local player = makeMock({ UserId = MOCK_USER_ID })
|
|
110
|
+
PlayerMock.writeLookup(player, "MarketplaceService.PlayerOwnsAssetAsync", ASSET_ID, true)
|
|
111
|
+
|
|
112
|
+
expect(awaitBool(MarketplaceUtils.promisePlayerOwnsAssetAsync(player, ASSET_ID))).toBe(true)
|
|
113
|
+
end)
|
|
114
|
+
|
|
115
|
+
it("reads a distinct domain from promisePlayerOwnsAsset", function()
|
|
116
|
+
local player = makeMock({ UserId = MOCK_USER_ID })
|
|
117
|
+
PlayerMock.writeLookup(player, "MarketplaceService.PlayerOwnsAsset", ASSET_ID, true)
|
|
118
|
+
|
|
119
|
+
expect(awaitBool(MarketplaceUtils.promisePlayerOwnsAssetAsync(player, ASSET_ID))).toBe(false)
|
|
120
|
+
end)
|
|
121
|
+
end)
|
|
122
|
+
|
|
123
|
+
describe("MarketplaceUtils.promisePlayerOwnsBundle", function()
|
|
124
|
+
it("resolves false for a mock with no injected ownership", function()
|
|
125
|
+
local player = makeMock({ UserId = MOCK_USER_ID })
|
|
126
|
+
|
|
127
|
+
expect(awaitBool(MarketplaceUtils.promisePlayerOwnsBundle(player, BUNDLE_ID))).toBe(false)
|
|
128
|
+
end)
|
|
129
|
+
|
|
130
|
+
it("resolves the ownership injected on the mock", function()
|
|
131
|
+
local player = makeMock({ UserId = MOCK_USER_ID })
|
|
132
|
+
PlayerMock.writeLookup(player, "MarketplaceService.PlayerOwnsBundle", BUNDLE_ID, true)
|
|
133
|
+
|
|
134
|
+
expect(awaitBool(MarketplaceUtils.promisePlayerOwnsBundle(player, BUNDLE_ID))).toBe(true)
|
|
135
|
+
end)
|
|
136
|
+
end)
|
|
137
|
+
|
|
138
|
+
describe("MarketplaceUtils.promiseUserSubscriptionStatus", function()
|
|
139
|
+
it("resolves an unsubscribed status for a mock with no injected status", function()
|
|
140
|
+
local player = makeMock({ UserId = MOCK_USER_ID })
|
|
141
|
+
|
|
142
|
+
local outcome, status =
|
|
143
|
+
PromiseTestUtils.awaitOutcome(MarketplaceUtils.promiseUserSubscriptionStatus(player, SUBSCRIPTION_ID))
|
|
144
|
+
expect(outcome).toBe("resolved")
|
|
145
|
+
expect(status.IsSubscribed).toBe(false)
|
|
146
|
+
expect(status.IsRenewing).toBe(false)
|
|
147
|
+
end)
|
|
148
|
+
|
|
149
|
+
it("resolves the status injected on the mock", function()
|
|
150
|
+
local player = makeMock({ UserId = MOCK_USER_ID })
|
|
151
|
+
PlayerMock.writeLookup(player, "MarketplaceService.GetUserSubscriptionStatusAsync", SUBSCRIPTION_ID, {
|
|
152
|
+
IsSubscribed = true,
|
|
153
|
+
IsRenewing = true,
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
local outcome, status =
|
|
157
|
+
PromiseTestUtils.awaitOutcome(MarketplaceUtils.promiseUserSubscriptionStatus(player, SUBSCRIPTION_ID))
|
|
158
|
+
expect(outcome).toBe("resolved")
|
|
159
|
+
expect(status.IsSubscribed).toBe(true)
|
|
160
|
+
expect(status.IsRenewing).toBe(true)
|
|
161
|
+
end)
|
|
162
|
+
|
|
163
|
+
it("keys the injected status by subscriptionId", function()
|
|
164
|
+
local player = makeMock({ UserId = MOCK_USER_ID })
|
|
165
|
+
PlayerMock.writeLookup(player, "MarketplaceService.GetUserSubscriptionStatusAsync", SUBSCRIPTION_ID, {
|
|
166
|
+
IsSubscribed = true,
|
|
167
|
+
IsRenewing = false,
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
local outcome, status =
|
|
171
|
+
PromiseTestUtils.awaitOutcome(MarketplaceUtils.promiseUserSubscriptionStatus(player, "EXP-other"))
|
|
172
|
+
expect(outcome).toBe("resolved")
|
|
173
|
+
expect(status.IsSubscribed).toBe(false)
|
|
174
|
+
end)
|
|
175
|
+
|
|
176
|
+
it("asserts on a non-player value", function()
|
|
177
|
+
expect(function()
|
|
178
|
+
MarketplaceUtils.promiseUserSubscriptionStatus(Instance.new("Folder") :: any, SUBSCRIPTION_ID)
|
|
179
|
+
end).toThrow("Bad player")
|
|
180
|
+
end)
|
|
181
|
+
|
|
182
|
+
it("asserts on a non-string subscriptionId", function()
|
|
183
|
+
local player = makeMock({ UserId = MOCK_USER_ID })
|
|
184
|
+
|
|
185
|
+
expect(function()
|
|
186
|
+
MarketplaceUtils.promiseUserSubscriptionStatus(player, 123 :: any)
|
|
187
|
+
end).toThrow("Bad subscriptionId")
|
|
188
|
+
end)
|
|
189
|
+
end)
|
|
190
|
+
|
|
191
|
+
describe("MarketplaceUtils.promiseProductInfo", function()
|
|
192
|
+
it("asserts on a non-number assetId", function()
|
|
193
|
+
expect(function()
|
|
194
|
+
MarketplaceUtils.promiseProductInfo("nope" :: any, Enum.InfoType.GamePass)
|
|
195
|
+
end).toThrow("Bad assetId")
|
|
196
|
+
end)
|
|
197
|
+
|
|
198
|
+
it("asserts on a non-EnumItem infoType", function()
|
|
199
|
+
expect(function()
|
|
200
|
+
MarketplaceUtils.promiseProductInfo(ASSET_ID, 5 :: any)
|
|
201
|
+
end).toThrow("Bad infoType")
|
|
202
|
+
end)
|
|
203
|
+
end)
|
|
204
|
+
|
|
205
|
+
describe("MarketplaceServiceCache.PromiseProductInfo", function()
|
|
206
|
+
it("asserts on a non-number productId", function()
|
|
207
|
+
expect(function()
|
|
208
|
+
MarketplaceServiceCache.PromiseProductInfo(
|
|
209
|
+
MarketplaceServiceCache :: any,
|
|
210
|
+
"nope" :: any,
|
|
211
|
+
Enum.InfoType.Asset
|
|
212
|
+
)
|
|
213
|
+
end).toThrow("Bad productId")
|
|
214
|
+
end)
|
|
215
|
+
end)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "MarketplaceUtilsTest",
|
|
3
|
+
"globIgnorePaths": [
|
|
4
|
+
"**/.package-lock.json",
|
|
5
|
+
"**/.pnpm",
|
|
6
|
+
"**/.pnpm-workspace-state-v1.json",
|
|
7
|
+
"**/.modules.yaml",
|
|
8
|
+
"**/.ignored",
|
|
9
|
+
"**/.ignored_*"
|
|
10
|
+
],
|
|
11
|
+
"tree": {
|
|
12
|
+
"$className": "DataModel",
|
|
13
|
+
"ServerScriptService": {
|
|
14
|
+
"$properties": {
|
|
15
|
+
"LoadStringEnabled": true
|
|
16
|
+
},
|
|
17
|
+
"marketplaceutils": {
|
|
18
|
+
"$path": ".."
|
|
19
|
+
},
|
|
20
|
+
"Script": {
|
|
21
|
+
"$path": "scripts/Server"
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
--[[
|
|
2
|
+
@class ServerMain
|
|
3
|
+
]]
|
|
4
|
+
local ServerScriptService = game:GetService("ServerScriptService")
|
|
5
|
+
|
|
6
|
+
local root = ServerScriptService.marketplaceutils
|
|
7
|
+
local loader = root:FindFirstChild("LoaderUtils", true).Parent
|
|
8
|
+
local require = require(loader).bootstrapGame(root)
|
|
9
|
+
|
|
10
|
+
local NevermoreTestRunnerUtils = require("NevermoreTestRunnerUtils")
|
|
11
|
+
if NevermoreTestRunnerUtils.runTestsIfNeededAsync(root) then
|
|
12
|
+
return
|
|
13
|
+
end
|