@quenty/datastore 13.40.0 → 13.42.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 +12 -12
- package/src/Server/DataStore.HookErrors.spec.lua +92 -0
- package/src/Server/DataStore.LoadErrors.spec.lua +133 -0
- package/src/Server/DataStore.Reentrance.spec.lua +119 -0
- package/src/Server/DataStore.SessionLock.spec.lua +478 -0
- package/src/Server/DataStore.SessionMessaging.spec.lua +71 -0
- package/src/Server/DataStore.TwoServerLock.spec.lua +190 -0
- package/src/Server/DataStore.lua +89 -18
- package/src/Server/DataStore.spec.lua +237 -0
- package/src/Server/DataStoreLockHelper.spec.lua +239 -0
- package/src/Server/DataStoreMessageHelper.spec.lua +134 -0
- package/src/Server/DataStoreNonRetryableLoadError.lua +61 -0
- package/src/Server/DataStoreTestUtils.lua +206 -0
- package/src/Server/GameDataStoreService.lua +26 -0
- package/src/Server/GameDataStoreService.spec.lua +215 -0
- package/src/Server/Mocks/DataStoreMock.lua +405 -0
- package/src/Server/Mocks/DataStoreMock.spec.lua +384 -0
- package/src/Server/Modules/DataStoreSnapshotUtils.spec.lua +91 -0
- package/src/Server/Modules/DataStoreStage.lua +8 -2
- package/src/Server/Modules/DataStoreStage.spec.lua +539 -0
- package/src/Server/Modules/DataStoreWriter.spec.lua +399 -0
- package/src/Server/PlayerDataStoreManager.RemovalCallbacks.spec.lua +99 -0
- package/src/Server/PlayerDataStoreManager.lua +36 -4
- package/src/Server/PlayerDataStoreManager.spec.lua +202 -0
- package/src/Server/PlayerDataStoreService.lua +18 -0
- package/src/Server/PlayerDataStoreService.spec.lua +218 -0
- package/src/Server/PrivateServerDataStoreService.lua +23 -1
- package/src/Server/PrivateServerDataStoreService.spec.lua +242 -0
- package/src/Server/Utility/DataStorePromises.lua +18 -5
- package/src/Server/Utility/DataStorePromises.spec.lua +363 -0
- package/src/Shared/Utility/DataStoreStringUtils.spec.lua +1 -1
- package/src/jest.config.lua +3 -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
|
+
# [13.42.0](https://github.com/Quenty/NevermoreEngine/compare/@quenty/datastore@13.41.0...@quenty/datastore@13.42.0) (2026-07-18)
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
- pin translation behavior with tests and fix camelCase key generation ([#737](https://github.com/Quenty/NevermoreEngine/issues/737)) ([1b7a536](https://github.com/Quenty/NevermoreEngine/commit/1b7a536dde7e124f8432e57612ec8138dd835d75))
|
|
11
|
+
|
|
12
|
+
# [13.41.0](https://github.com/Quenty/NevermoreEngine/compare/@quenty/datastore@13.40.0...@quenty/datastore@13.41.0) (2026-07-18)
|
|
13
|
+
|
|
14
|
+
**Note:** Version bump only for package @quenty/datastore
|
|
15
|
+
|
|
6
16
|
# [13.40.0](https://github.com/Quenty/NevermoreEngine/compare/@quenty/datastore@13.39.1...@quenty/datastore@13.40.0) (2026-07-14)
|
|
7
17
|
|
|
8
18
|
**Note:** Version bump only for package @quenty/datastore
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quenty/datastore",
|
|
3
|
-
"version": "13.
|
|
3
|
+
"version": "13.42.0",
|
|
4
4
|
"description": "Quenty's Datastore implementation for Roblox",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"Roblox",
|
|
@@ -29,26 +29,26 @@
|
|
|
29
29
|
"Quenty"
|
|
30
30
|
],
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@quenty/baseobject": "10.
|
|
33
|
-
"@quenty/bindtocloseservice": "8.
|
|
32
|
+
"@quenty/baseobject": "10.14.0",
|
|
33
|
+
"@quenty/bindtocloseservice": "8.35.0",
|
|
34
34
|
"@quenty/loader": "10.11.0",
|
|
35
|
-
"@quenty/maid": "3.
|
|
35
|
+
"@quenty/maid": "3.10.0",
|
|
36
36
|
"@quenty/math": "2.7.5",
|
|
37
|
-
"@quenty/messagingserviceutils": "7.
|
|
37
|
+
"@quenty/messagingserviceutils": "7.24.0",
|
|
38
38
|
"@quenty/nevermore-test-runner": "1.4.0",
|
|
39
|
-
"@quenty/pagesutils": "5.
|
|
40
|
-
"@quenty/promise": "10.
|
|
41
|
-
"@quenty/promisemaid": "5.
|
|
42
|
-
"@quenty/rx": "13.
|
|
43
|
-
"@quenty/servicebag": "11.
|
|
39
|
+
"@quenty/pagesutils": "5.21.0",
|
|
40
|
+
"@quenty/promise": "10.20.0",
|
|
41
|
+
"@quenty/promisemaid": "5.20.0",
|
|
42
|
+
"@quenty/rx": "13.30.0",
|
|
43
|
+
"@quenty/servicebag": "11.19.0",
|
|
44
44
|
"@quenty/signal": "7.13.1",
|
|
45
45
|
"@quenty/symbol": "3.5.2",
|
|
46
46
|
"@quenty/table": "3.9.2",
|
|
47
|
-
"@quenty/valueobject": "13.
|
|
47
|
+
"@quenty/valueobject": "13.33.0",
|
|
48
48
|
"@quentystudios/jest-lua": "3.10.0-quenty.2"
|
|
49
49
|
},
|
|
50
50
|
"publishConfig": {
|
|
51
51
|
"access": "public"
|
|
52
52
|
},
|
|
53
|
-
"gitHead": "
|
|
53
|
+
"gitHead": "647acfad93dca41c38f59cda247d2bfc0fff36d4"
|
|
54
54
|
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
--!nonstrict
|
|
2
|
+
--[[
|
|
3
|
+
Characterizes how the datastore layer handles user-supplied saving callbacks that misbehave:
|
|
4
|
+
throw, return a rejected promise, or yield forever. Several are genuine data-integrity failure
|
|
5
|
+
modes, so pinning them documents the contract callers must honor. The removing-callback matrix
|
|
6
|
+
lives with the manager it exercises in PlayerDataStoreManager.RemovalCallbacks.spec.lua.
|
|
7
|
+
|
|
8
|
+
@class DataStoreHookErrors.spec.lua
|
|
9
|
+
]]
|
|
10
|
+
local require = require(script.Parent.loader).load(script)
|
|
11
|
+
|
|
12
|
+
local DataStoreTestUtils = require("DataStoreTestUtils")
|
|
13
|
+
local Jest = require("Jest")
|
|
14
|
+
local Promise = require("Promise")
|
|
15
|
+
local PromiseTestUtils = require("PromiseTestUtils")
|
|
16
|
+
|
|
17
|
+
local describe = Jest.Globals.describe
|
|
18
|
+
local expect = Jest.Globals.expect
|
|
19
|
+
local it = Jest.Globals.it
|
|
20
|
+
|
|
21
|
+
describe("DataStore saving callbacks that misbehave", function()
|
|
22
|
+
it("runs a well-behaved saving callback and completes the save", function()
|
|
23
|
+
local controller = DataStoreTestUtils.setup()
|
|
24
|
+
local dataStore = controller.newDataStore("key")
|
|
25
|
+
|
|
26
|
+
local ran = false
|
|
27
|
+
dataStore:AddSavingCallback(function()
|
|
28
|
+
ran = true
|
|
29
|
+
end)
|
|
30
|
+
dataStore:Store("x", 1)
|
|
31
|
+
|
|
32
|
+
local promise = dataStore:Save()
|
|
33
|
+
if not PromiseTestUtils.awaitSettled(promise, 5) then
|
|
34
|
+
expect("hung").toEqual("settled")
|
|
35
|
+
controller:destroy()
|
|
36
|
+
return
|
|
37
|
+
end
|
|
38
|
+
expect((promise:Yield())).toEqual(true)
|
|
39
|
+
expect(ran).toEqual(true)
|
|
40
|
+
|
|
41
|
+
controller:destroy()
|
|
42
|
+
end)
|
|
43
|
+
|
|
44
|
+
it("isolates a throwing saving callback into a clean save rejection, preserving the stack trace", function()
|
|
45
|
+
local controller = DataStoreTestUtils.setup()
|
|
46
|
+
local dataStore = controller.newDataStore("key")
|
|
47
|
+
|
|
48
|
+
dataStore:AddSavingCallback(function()
|
|
49
|
+
error("saving callback boom")
|
|
50
|
+
end)
|
|
51
|
+
dataStore:Store("x", 1)
|
|
52
|
+
|
|
53
|
+
local outcome, err = PromiseTestUtils.awaitOutcome(dataStore:Save(), 5)
|
|
54
|
+
expect(outcome).toEqual("rejected")
|
|
55
|
+
|
|
56
|
+
-- The rejection preserves the original message and the invoking frame, so the failure stays debuggable.
|
|
57
|
+
expect(string.find(tostring(err), "saving callback boom", 1, true) ~= nil).toEqual(true)
|
|
58
|
+
expect(string.find(tostring(err), "PromiseInvokeSavingCallbacks", 1, true) ~= nil).toEqual(true)
|
|
59
|
+
|
|
60
|
+
controller:destroy()
|
|
61
|
+
end)
|
|
62
|
+
|
|
63
|
+
it("rejects the save when a saving callback returns a rejected promise (and does not persist)", function()
|
|
64
|
+
local controller = DataStoreTestUtils.setup()
|
|
65
|
+
local dataStore = controller.newDataStore("key")
|
|
66
|
+
|
|
67
|
+
dataStore:AddSavingCallback(function()
|
|
68
|
+
return Promise.rejected("nope")
|
|
69
|
+
end)
|
|
70
|
+
dataStore:Store("x", 1)
|
|
71
|
+
|
|
72
|
+
expect((PromiseTestUtils.awaitOutcome(dataStore:Save(), 5))).toEqual("rejected")
|
|
73
|
+
expect(controller.mock:GetRaw("key")).toEqual(nil)
|
|
74
|
+
|
|
75
|
+
controller:destroy()
|
|
76
|
+
end)
|
|
77
|
+
|
|
78
|
+
it("blocks the save while a saving callback yields (never resolves)", function()
|
|
79
|
+
local controller = DataStoreTestUtils.setup()
|
|
80
|
+
local dataStore = controller.newDataStore("key")
|
|
81
|
+
|
|
82
|
+
dataStore:AddSavingCallback(function()
|
|
83
|
+
return Promise.new()
|
|
84
|
+
end)
|
|
85
|
+
dataStore:Store("x", 1)
|
|
86
|
+
|
|
87
|
+
local promise = dataStore:Save()
|
|
88
|
+
expect(PromiseTestUtils.awaitSettled(promise, 2)).toEqual(false)
|
|
89
|
+
|
|
90
|
+
controller:destroy()
|
|
91
|
+
end)
|
|
92
|
+
end)
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
--!nonstrict
|
|
2
|
+
--[[
|
|
3
|
+
Characterizes the shared load promise and the auto-save loop. The load view is cached
|
|
4
|
+
(PromiseViewUpToDate returns one shared promise), so every Load/LoadAll/Store/Observe attaches
|
|
5
|
+
its own continuation to the same promise; these tests pin that a settled load fans out its
|
|
6
|
+
resolution or rejection to every consumer, including ones attached mid-yield.
|
|
7
|
+
|
|
8
|
+
@class DataStoreLoadErrors.spec.lua
|
|
9
|
+
]]
|
|
10
|
+
local require = require(script.Parent.loader).load(script)
|
|
11
|
+
|
|
12
|
+
local DataStoreTestUtils = require("DataStoreTestUtils")
|
|
13
|
+
local Jest = require("Jest")
|
|
14
|
+
local PromiseTestUtils = require("PromiseTestUtils")
|
|
15
|
+
|
|
16
|
+
local describe = Jest.Globals.describe
|
|
17
|
+
local expect = Jest.Globals.expect
|
|
18
|
+
local it = Jest.Globals.it
|
|
19
|
+
|
|
20
|
+
describe("shared load promise fan-out", function()
|
|
21
|
+
it("delivers the resolution to every consumer of the shared load", function()
|
|
22
|
+
local controller = DataStoreTestUtils.setup()
|
|
23
|
+
controller.mock:SetRaw("key", { a = 1, b = 2 })
|
|
24
|
+
local dataStore = controller.newDataStore("key")
|
|
25
|
+
|
|
26
|
+
local valueA, valueB, all
|
|
27
|
+
dataStore:Load("a"):Then(function(value)
|
|
28
|
+
valueA = value
|
|
29
|
+
end)
|
|
30
|
+
dataStore:Load("b"):Then(function(value)
|
|
31
|
+
valueB = value
|
|
32
|
+
end)
|
|
33
|
+
dataStore:LoadAll():Then(function(value)
|
|
34
|
+
all = value
|
|
35
|
+
end)
|
|
36
|
+
|
|
37
|
+
expect(PromiseTestUtils.awaitValue(function()
|
|
38
|
+
return valueA ~= nil and valueB ~= nil and all ~= nil
|
|
39
|
+
end, 5)).toEqual(true)
|
|
40
|
+
expect(valueA).toEqual(1)
|
|
41
|
+
expect(valueB).toEqual(2)
|
|
42
|
+
expect(all).toEqual({ a = 1, b = 2 })
|
|
43
|
+
|
|
44
|
+
controller:destroy()
|
|
45
|
+
end)
|
|
46
|
+
|
|
47
|
+
it("resolves all consumers of a slow (yielding) load attached during the yield", function()
|
|
48
|
+
local controller = DataStoreTestUtils.setup()
|
|
49
|
+
controller.mock:SetRaw("key", { a = 1 })
|
|
50
|
+
controller.mock:SetYieldTime(0.3)
|
|
51
|
+
local dataStore = controller.newDataStore("key")
|
|
52
|
+
|
|
53
|
+
local results = {}
|
|
54
|
+
for i = 1, 3 do
|
|
55
|
+
dataStore:Load("a"):Then(function(value)
|
|
56
|
+
results[i] = value
|
|
57
|
+
end)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
expect(PromiseTestUtils.awaitValue(function()
|
|
61
|
+
return results[1] ~= nil and results[2] ~= nil and results[3] ~= nil
|
|
62
|
+
end, 5)).toEqual(true)
|
|
63
|
+
expect(results[1]).toEqual(1)
|
|
64
|
+
expect(results[2]).toEqual(1)
|
|
65
|
+
expect(results[3]).toEqual(1)
|
|
66
|
+
|
|
67
|
+
controller:destroy()
|
|
68
|
+
end)
|
|
69
|
+
|
|
70
|
+
it("delivers the rejection to every consumer when the shared load fails", function()
|
|
71
|
+
local controller = DataStoreTestUtils.setup()
|
|
72
|
+
controller.mock:FailAllRequests()
|
|
73
|
+
local dataStore = controller.newDataStore("key")
|
|
74
|
+
|
|
75
|
+
local aRejected, bRejected = false, false
|
|
76
|
+
dataStore:Load("a"):Then(nil, function()
|
|
77
|
+
aRejected = true
|
|
78
|
+
end)
|
|
79
|
+
dataStore:Load("b"):Then(nil, function()
|
|
80
|
+
bRejected = true
|
|
81
|
+
end)
|
|
82
|
+
|
|
83
|
+
expect(PromiseTestUtils.awaitValue(function()
|
|
84
|
+
return aRejected and bRejected
|
|
85
|
+
end, 5)).toEqual(true)
|
|
86
|
+
|
|
87
|
+
controller:destroy()
|
|
88
|
+
end)
|
|
89
|
+
end)
|
|
90
|
+
|
|
91
|
+
describe("auto-save loop", function()
|
|
92
|
+
it("persists staged data on the auto-save schedule without an explicit Save", function()
|
|
93
|
+
local controller = DataStoreTestUtils.setup()
|
|
94
|
+
local dataStore = controller.newDataStore("key")
|
|
95
|
+
dataStore:SetAutoSaveTimeSeconds(0.2)
|
|
96
|
+
|
|
97
|
+
-- Store triggers the initial load; once loaded, the auto-save loop starts and flushes.
|
|
98
|
+
dataStore:Store("coins", 5)
|
|
99
|
+
|
|
100
|
+
local saved = PromiseTestUtils.awaitValue(function()
|
|
101
|
+
local raw = controller.mock:GetRaw("key")
|
|
102
|
+
return raw ~= nil and raw.coins == 5
|
|
103
|
+
end, 6)
|
|
104
|
+
expect(saved).toEqual(true)
|
|
105
|
+
|
|
106
|
+
controller:destroy()
|
|
107
|
+
end)
|
|
108
|
+
|
|
109
|
+
it("stops auto-saving after the datastore is destroyed", function()
|
|
110
|
+
local controller = DataStoreTestUtils.setup()
|
|
111
|
+
local dataStore = controller.newDataStore("key")
|
|
112
|
+
dataStore:SetAutoSaveTimeSeconds(0.2)
|
|
113
|
+
dataStore:Store("coins", 5)
|
|
114
|
+
|
|
115
|
+
expect(PromiseTestUtils.awaitValue(function()
|
|
116
|
+
local raw = controller.mock:GetRaw("key")
|
|
117
|
+
return raw ~= nil and raw.coins == 5
|
|
118
|
+
end, 6)).toEqual(true)
|
|
119
|
+
|
|
120
|
+
dataStore:Destroy()
|
|
121
|
+
|
|
122
|
+
-- After destroy the loop is gone: record the call count, wait past several auto-save
|
|
123
|
+
-- intervals, and confirm no new saves land.
|
|
124
|
+
local before = controller.mock:GetCallCount("UpdateAsync")
|
|
125
|
+
local settled = PromiseTestUtils.awaitValue(function()
|
|
126
|
+
return false
|
|
127
|
+
end, 1)
|
|
128
|
+
expect(settled).toEqual(false)
|
|
129
|
+
expect(controller.mock:GetCallCount("UpdateAsync")).toEqual(before)
|
|
130
|
+
|
|
131
|
+
controller:destroy()
|
|
132
|
+
end)
|
|
133
|
+
end)
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
--!nonstrict
|
|
2
|
+
--[[
|
|
3
|
+
Re-entrance and cancellation: when a datastore request is in flight (yielding) and its owning
|
|
4
|
+
maid is torn down (the DataStore is destroyed / the player leaves), the request thread must be
|
|
5
|
+
cancelled and never resume to write. A session-locked load can legitimately stay pending for a
|
|
6
|
+
long time while a lock command is outstanding.
|
|
7
|
+
|
|
8
|
+
@class DataStoreReentrance.spec.lua
|
|
9
|
+
]]
|
|
10
|
+
local require = require(script.Parent.loader).load(script)
|
|
11
|
+
|
|
12
|
+
local DataStoreTestUtils = require("DataStoreTestUtils")
|
|
13
|
+
local Jest = require("Jest")
|
|
14
|
+
local PromiseTestUtils = require("PromiseTestUtils")
|
|
15
|
+
|
|
16
|
+
local describe = Jest.Globals.describe
|
|
17
|
+
local expect = Jest.Globals.expect
|
|
18
|
+
local it = Jest.Globals.it
|
|
19
|
+
|
|
20
|
+
describe("in-flight request cancellation (maid teardown)", function()
|
|
21
|
+
it("cancels a yielding load thread when the DataStore is destroyed (no leaked thread)", function()
|
|
22
|
+
local controller = DataStoreTestUtils.setup()
|
|
23
|
+
controller.mock:BlockRequests() -- the lock-acquire request hangs, so the load is stuck in flight
|
|
24
|
+
|
|
25
|
+
local dataStore = controller.newSessionLockedStore()
|
|
26
|
+
local promise = dataStore:PromiseLoadSuccessful()
|
|
27
|
+
|
|
28
|
+
expect(PromiseTestUtils.awaitSettled(promise, 1)).toEqual(false)
|
|
29
|
+
|
|
30
|
+
-- Destroy while the request thread is yielding -- its maid must cancel it.
|
|
31
|
+
dataStore:Destroy()
|
|
32
|
+
|
|
33
|
+
-- A properly cancelled thread never resumes, so the lock is never written. A leaked thread
|
|
34
|
+
-- would resume on unblock and write the lock envelope.
|
|
35
|
+
controller.mock:UnblockRequests()
|
|
36
|
+
local everWrote = PromiseTestUtils.awaitValue(function()
|
|
37
|
+
return controller.mock:GetRaw("player_1") ~= nil
|
|
38
|
+
end, 2)
|
|
39
|
+
expect(everWrote).toEqual(false)
|
|
40
|
+
|
|
41
|
+
controller:destroy()
|
|
42
|
+
end)
|
|
43
|
+
|
|
44
|
+
it("cancels a yielding save thread when the DataStore is destroyed", function()
|
|
45
|
+
local controller = DataStoreTestUtils.setup()
|
|
46
|
+
|
|
47
|
+
-- Load cleanly first (acquires the lock), then block so the SAVE request hangs in flight.
|
|
48
|
+
local dataStore = controller.newSessionLockedStore()
|
|
49
|
+
local loadPromise = dataStore:PromiseLoadSuccessful()
|
|
50
|
+
if not PromiseTestUtils.awaitSettled(loadPromise, 10) then
|
|
51
|
+
expect("load hung").toEqual("load settled")
|
|
52
|
+
controller:destroy()
|
|
53
|
+
return
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
controller.mock:BlockRequests()
|
|
57
|
+
dataStore:Store("coins", 5)
|
|
58
|
+
local savePromise = dataStore:Save()
|
|
59
|
+
expect(PromiseTestUtils.awaitSettled(savePromise, 1)).toEqual(false)
|
|
60
|
+
|
|
61
|
+
local versionsBefore = controller.mock:GetCallCount("UpdateAsync")
|
|
62
|
+
dataStore:Destroy()
|
|
63
|
+
controller.mock:UnblockRequests()
|
|
64
|
+
|
|
65
|
+
-- The cancelled save thread must not resume and perform another UpdateAsync write.
|
|
66
|
+
local extraWrites = PromiseTestUtils.awaitValue(function()
|
|
67
|
+
return controller.mock:GetCallCount("UpdateAsync") > versionsBefore + 1
|
|
68
|
+
end, 2)
|
|
69
|
+
expect(extraWrites).toEqual(false)
|
|
70
|
+
|
|
71
|
+
controller:destroy()
|
|
72
|
+
end)
|
|
73
|
+
end)
|
|
74
|
+
|
|
75
|
+
describe("lock command that does not settle", function()
|
|
76
|
+
it("keeps the load pending while the lock command is outstanding, then completes when it settles", function()
|
|
77
|
+
local controller = DataStoreTestUtils.setup()
|
|
78
|
+
controller.mock:BlockRequests() -- simulate a lock command that hasn't propagated yet (up to ~30s)
|
|
79
|
+
|
|
80
|
+
local dataStore = controller.newSessionLockedStore()
|
|
81
|
+
local promise = dataStore:PromiseLoadSuccessful()
|
|
82
|
+
|
|
83
|
+
-- Must NOT spuriously resolve or error while the lock command is outstanding.
|
|
84
|
+
expect(PromiseTestUtils.awaitSettled(promise, 2)).toEqual(false)
|
|
85
|
+
|
|
86
|
+
controller.mock:UnblockRequests()
|
|
87
|
+
if not PromiseTestUtils.awaitSettled(promise, 10) then
|
|
88
|
+
expect("hung after unblock").toEqual("settled")
|
|
89
|
+
controller:destroy()
|
|
90
|
+
return
|
|
91
|
+
end
|
|
92
|
+
expect((promise:Wait())).toEqual(true)
|
|
93
|
+
|
|
94
|
+
controller:destroy()
|
|
95
|
+
end)
|
|
96
|
+
|
|
97
|
+
it("re-loads cleanly on a fresh session after a cancelled in-flight lock command", function()
|
|
98
|
+
local controller = DataStoreTestUtils.setup()
|
|
99
|
+
controller.mock:BlockRequests()
|
|
100
|
+
|
|
101
|
+
local first = controller.newSessionLockedStore()
|
|
102
|
+
local firstPromise = first:PromiseLoadSuccessful()
|
|
103
|
+
expect(PromiseTestUtils.awaitSettled(firstPromise, 1)).toEqual(false)
|
|
104
|
+
first:Destroy() -- cancel the in-flight lock command
|
|
105
|
+
controller.mock:UnblockRequests()
|
|
106
|
+
|
|
107
|
+
-- The cancelled attempt left no lock, so a fresh session acquires cleanly.
|
|
108
|
+
local second = controller.newSessionLockedStore()
|
|
109
|
+
local secondPromise = second:PromiseLoadSuccessful()
|
|
110
|
+
if not PromiseTestUtils.awaitSettled(secondPromise, 10) then
|
|
111
|
+
expect("hung").toEqual("settled")
|
|
112
|
+
controller:destroy()
|
|
113
|
+
return
|
|
114
|
+
end
|
|
115
|
+
expect((secondPromise:Wait())).toEqual(true)
|
|
116
|
+
|
|
117
|
+
controller:destroy()
|
|
118
|
+
end)
|
|
119
|
+
end)
|