Join the RoClicks Discord
RoClicks

mock_datastore.luau

Luau, 81 lines from Engineering overview

Download mock_datastore.luau
--!nocheck
-- Mock DataStore + fake clock for plain-luau tests of save code (no Roblox needed).
--   local M = require("./mock_datastore")
--   local env = M.env()                 -- env.clock / env.time / env.wait / env.spawn / env.warn
--   local store = M.store()             -- store.data[key], store.fail(msg, n), store.calls
--   local p = M.player(1)               -- p.kicked holds the Kick message
-- task.wait advances the fake clock instantly and calls env.onWait(), so "another server releases the lock
-- after 3 waits" is one line and a 30 s wait costs no real time.
local M = {}

local function deep(t)
	if type(t) ~= "table" then return t end
	local o = {}
	for k, v in pairs(t) do o[k] = deep(v) end
	return o
end
M.deep = deep

function M.store()
	local s = { data = {}, calls = 0, queue = {} }
	-- throw `msg` on the next n calls (e.g. "502: API Services rejected request with error. HTTP 500")
	function s.fail(msg, n)
		for _ = 1, n or 1 do table.insert(s.queue, msg) end
	end
	function s:UpdateAsync(key, fn)
		self.calls += 1
		if #self.queue > 0 then error(table.remove(self.queue, 1), 0) end
		-- real DataStores never share tables with the caller: copy in and out
		local v = fn(deep(self.data[key]))
		if v ~= nil then self.data[key] = deep(v) end
		return deep(v)
	end
	function s:GetAsync(key)
		self.calls += 1
		if #self.queue > 0 then error(table.remove(self.queue, 1), 0) end
		return deep(self.data[key])
	end
	function s:SetAsync(key, v)
		self.calls += 1
		if #self.queue > 0 then error(table.remove(self.queue, 1), 0) end
		self.data[key] = deep(v)
	end
	return s
end

function M.env()
	local e = { t = 0, epoch = 1000000, warns = {}, onWait = nil }
	e.clock = function() return e.t end
	e.time = function() return e.epoch + math.floor(e.t) end
	e.wait = function(n)
		e.t += (n or 0.03)
		if e.onWait then e.onWait() end
		return n
	end
	e.spawn = function(f, ...) return f(...) end -- run inline: deterministic order
	e.warn = function(...) table.insert(e.warns, table.concat({ ... }, " ")) end
	return e
end

function M.player(id)
	local p = { UserId = id, Name = "Player" .. id, Parent = true, kicked = nil }
	function p:Kick(msg) self.kicked = msg; self.Parent = nil end
	return p
end

-- tiny assertion helper: collects failures, prints PASS/FAIL lines
function M.checker()
	local c = { failed = 0 }
	function c.check(name, cond)
		print((cond and "PASS " or "FAIL ") .. name)
		if not cond then c.failed += 1 end
	end
	function c.done()
		if c.failed > 0 then error(c.failed .. " check(s) failed") end
		print("all checks passed")
	end
	return c
end

return M