-- SPDX-FileCopyrightText: © 2026 Vladimir Zorin -- SPDX-License-Identifier: OWL-1.0 or later local testimony = require("testimony") local std = require("std") local mneme = require("mneme") local sleep = function(sec) os.execute("sleep " .. sec) end local testify = testimony.new("== mneme: TTL and expiry ==") local tmp_path = function() return "/tmp/mneme_ttl_" .. std.nanoid() .. ".mneme" end testify:that("set with TTL, get before expiry succeeds", function() local path = tmp_path() local db = mneme.open(path, { sync = "none" }) local ks = db:keyspace("t") ks:set("session", "data", { ttl = 60 }) local val = ks:get("session") testimony.assert_equal("data", val) db:close() std.fs.remove(path) end) testify:that("set with TTL=1, get after expiry returns nil", function() local path = tmp_path() local db = mneme.open(path, { sync = "none" }) local ks = db:keyspace("t") ks:set("ephemeral", "gone_soon", { ttl = 1 }) -- Immediately should be available testimony.assert_equal("gone_soon", ks:get("ephemeral")) -- Wait for expiry sleep(2) -- Should be expired now local val = ks:get("ephemeral") testimony.assert_nil(val) db:close() std.fs.remove(path) end) testify:that("ttl() returns remaining seconds", function() local path = tmp_path() local db = mneme.open(path, { sync = "none" }) local ks = db:keyspace("t") ks:set("k", "v", { ttl = 60 }) local remaining = ks:ttl("k") testimony.assert_not_nil(remaining) testimony.assert_true(remaining > 55 and remaining <= 60) db:close() std.fs.remove(path) end) testify:that("ttl() returns nil for key without TTL", function() local path = tmp_path() local db = mneme.open(path, { sync = "none" }) local ks = db:keyspace("t") ks:set("no_ttl", "forever") local remaining = ks:ttl("no_ttl") testimony.assert_nil(remaining) db:close() std.fs.remove(path) end) testify:that("persist() removes TTL", function() local path = tmp_path() local db = mneme.open(path, { sync = "none" }) local ks = db:keyspace("t") ks:set("k", "v", { ttl = 2 }) testimony.assert_not_nil(ks:ttl("k")) ks:persist("k") testimony.assert_nil(ks:ttl("k")) -- Should survive past original TTL sleep(3) testimony.assert_equal("v", ks:get("k")) db:close() std.fs.remove(path) end) testify:that("purge_expired removes expired keys", function() local path = tmp_path() local db = mneme.open(path, { sync = "none" }) local ks = db:keyspace("t") ks:set("live", "yes", { ttl = 60 }) ks:set("dead1", "no", { ttl = 1 }) ks:set("dead2", "no", { ttl = 1 }) ks:set("permanent", "always") sleep(2) local purged = ks:purge_expired() testimony.assert_equal(2, purged) -- Live and permanent keys still exist testimony.assert_equal("yes", ks:get("live")) testimony.assert_equal("always", ks:get("permanent")) -- Dead keys are gone even from scan testimony.assert_equal(2, ks:count()) db:close() std.fs.remove(path) end) testify:that("scan skips expired keys", function() local path = tmp_path() local db = mneme.open(path, { sync = "none" }) local ks = db:keyspace("t") ks:set("a", "alive") ks:set("b", "expired", { ttl = 1 }) ks:set("c", "alive") sleep(2) local keys = {} for k in ks:scan() do table.insert(keys, k) end testimony.assert_equal(2, #keys) testimony.assert_equal("a", keys[1]) testimony.assert_equal("c", keys[2]) db:close() std.fs.remove(path) end) testify:conclude()