@quenty/marketplaceutils 11.24.0 → 11.25.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 CHANGED
@@ -3,6 +3,12 @@
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.0](https://github.com/Quenty/NevermoreEngine/compare/@quenty/marketplaceutils@11.24.0...@quenty/marketplaceutils@11.25.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
+
6
12
  # [11.24.0](https://github.com/Quenty/NevermoreEngine/compare/@quenty/marketplaceutils@11.23.0...@quenty/marketplaceutils@11.24.0) (2026-07-20)
7
13
 
8
14
  ### Features
@@ -0,0 +1,10 @@
1
+ {
2
+ "targets": {
3
+ "test": {
4
+ "universeId": 9716264427,
5
+ "placeId": 121084030937626,
6
+ "project": "test/default.project.json",
7
+ "scriptTemplate": "test/scripts/Server/ServerMain.server.lua"
8
+ }
9
+ }
10
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quenty/marketplaceutils",
3
- "version": "11.24.0",
3
+ "version": "11.25.0",
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/promise": "10.20.0",
35
- "@quenty/servicebag": "11.19.0"
34
+ "@quenty/nevermore-test-runner": "1.5.0",
35
+ "@quenty/playermock": "1.1.0",
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": "2c903e91ae2ea1702ae1ce94af20af83af81e4fe"
43
+ "gitHead": "1de37218a2bedb8e3f8614a2e09bba9eddc812da"
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
- subStatus = MarketplaceService:GetUserSubscriptionStatusAsync(player, subscriptionId)
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
- result = MarketplaceService:UserOwnsGamePassAsync(userId, gamePassId)
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
- result = MarketplaceService:PlayerOwnsAsset(player, assetId)
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
- result = MarketplaceService:PlayerOwnsAssetAsync(player, assetId)
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
- result = MarketplaceService:PlayerOwnsBundle(player, bundleId)
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,3 @@
1
+ return {
2
+ testMatch = { "**/*.spec" },
3
+ }
@@ -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