snapshot of https://github.com/jitsi/jitsi-meet.git @ fd1749dd28f4138aa80234fc7f311b19aaffa0e7 (2026-09-15)
This commit is contained in:
919
tests/prosody/lua/util_spec.lua
Normal file
919
tests/prosody/lua/util_spec.lua
Normal file
@@ -0,0 +1,919 @@
|
||||
-- Unit tests for util.lib.lua
|
||||
-- Run with busted from resources/prosody-plugins/:
|
||||
-- busted spec/lua/
|
||||
--
|
||||
-- Stubs every Prosody dependency so no Prosody installation is needed.
|
||||
-- Functions that rely on live Prosody objects (async HTTP, room lookup,
|
||||
-- stanza mutation, etc.) are not covered here — they are exercised by the
|
||||
-- integration tests.
|
||||
--
|
||||
-- Module is loaded with:
|
||||
-- muc_domain_prefix = "conference" (default)
|
||||
-- muc_domain_base = "test.example.com" (module.host default)
|
||||
-- muc_domain = "conference.test.example.com"
|
||||
-- moderated_subdomains = {"moderated-subdomain"}
|
||||
-- moderated_rooms = {"moderatedroom"}
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- Minimal Set implementation (mirrors Prosody's util.set behaviour)
|
||||
-- ---------------------------------------------------------------------------
|
||||
|
||||
local Set_mt = {}
|
||||
Set_mt.__index = Set_mt
|
||||
-- set / fn → new set with fn mapped over every element
|
||||
Set_mt.__div = function(self, fn)
|
||||
local new_s = setmetatable({ _data = {}, _is_set = true }, Set_mt)
|
||||
for k in pairs(self._data) do
|
||||
local mapped = fn(k)
|
||||
if mapped ~= nil then new_s._data[mapped] = true end
|
||||
end
|
||||
return new_s
|
||||
end
|
||||
function Set_mt:contains(v) return self._data[v] == true end
|
||||
function Set_mt:empty() return next(self._data) == nil end
|
||||
function Set_mt:items() return pairs(self._data) end
|
||||
|
||||
local function new_set(list)
|
||||
local s = setmetatable({ _data = {}, _is_set = true }, Set_mt)
|
||||
for _, v in ipairs(list or {}) do s._data[v] = true end
|
||||
return s
|
||||
end
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- Package preloads — must be registered before dofile()
|
||||
-- ---------------------------------------------------------------------------
|
||||
|
||||
package.preload['util.set'] = function()
|
||||
return {
|
||||
is_set = function(v) return type(v) == 'table' and v._is_set == true end,
|
||||
new = new_set,
|
||||
}
|
||||
end
|
||||
|
||||
package.preload['util.jid'] = function()
|
||||
local function jid_split(j)
|
||||
if not j then return nil, nil, nil end
|
||||
local node, host, resource
|
||||
local at = j:find('@')
|
||||
local slash
|
||||
if at then
|
||||
slash = j:find('/', at + 1)
|
||||
node = j:sub(1, at - 1)
|
||||
if slash then
|
||||
host = j:sub(at + 1, slash - 1)
|
||||
resource = j:sub(slash + 1)
|
||||
else
|
||||
host = j:sub(at + 1)
|
||||
end
|
||||
else
|
||||
slash = j:find('/')
|
||||
if slash then
|
||||
host = j:sub(1, slash - 1)
|
||||
resource = j:sub(slash + 1)
|
||||
else
|
||||
host = j
|
||||
end
|
||||
end
|
||||
node = (node and #node > 0) and node or nil
|
||||
host = (host and #host > 0) and host or nil
|
||||
resource = (resource and #resource > 0) and resource or nil
|
||||
return node, host, resource
|
||||
end
|
||||
|
||||
return {
|
||||
split = jid_split,
|
||||
join = function(node, host, resource)
|
||||
local j = ''
|
||||
if node then j = node .. '@' end
|
||||
j = j .. (host or '')
|
||||
if resource then j = j .. '/' .. resource end
|
||||
return j
|
||||
end,
|
||||
prep = function(j) return j end,
|
||||
bare = function(j)
|
||||
if not j then return nil end
|
||||
local node, host = jid_split(j)
|
||||
if node then return node .. '@' .. (host or '') end
|
||||
return host
|
||||
end,
|
||||
node = function(j)
|
||||
if not j then return nil end
|
||||
local node = jid_split(j)
|
||||
return node
|
||||
end,
|
||||
}
|
||||
end
|
||||
|
||||
package.preload['util.cache'] = function()
|
||||
return {
|
||||
new = function(_size)
|
||||
local t = {}
|
||||
return {
|
||||
get = function(_, k) return t[k] end,
|
||||
set = function(_, k, v) t[k] = v end,
|
||||
}
|
||||
end,
|
||||
}
|
||||
end
|
||||
|
||||
package.preload['util.array'] = function()
|
||||
local Array_mt = {}
|
||||
Array_mt.__index = Array_mt
|
||||
function Array_mt:push(v)
|
||||
self[#self + 1] = v
|
||||
return self
|
||||
end
|
||||
return function()
|
||||
return setmetatable({}, Array_mt)
|
||||
end
|
||||
end
|
||||
|
||||
-- Modules used only by functions we do not test — empty stubs suffice.
|
||||
package.preload['util.stanza'] = function() return {} end
|
||||
package.preload['util.timer'] = function() return { add_task = function() end } end
|
||||
package.preload['util.async'] = function() return {} end
|
||||
package.preload['net.http'] = function() return {} end
|
||||
package.preload['net.http.server'] = function() return {} end
|
||||
package.preload['core.usermanager'] = function() return {} end
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- Global stubs required by util.lib.lua at module load time
|
||||
--
|
||||
-- Busted sandboxes spec files via setfenv: bare assignments like
|
||||
-- module = {...}
|
||||
-- go into the busted context env, not _G. But dofile() always runs chunks
|
||||
-- in _G. So any global that util.lib.lua reads at load time must be written
|
||||
-- directly into _G via _G.key = value.
|
||||
-- package.preload is exempt — setting a key there mutates the real table.
|
||||
-- ---------------------------------------------------------------------------
|
||||
|
||||
_G.prosody = { version = "test", platform = "posix" }
|
||||
_G.hosts = {}
|
||||
|
||||
local mod = { host = "test.example.com" }
|
||||
|
||||
mod.log = function() end
|
||||
mod.get_option_string = function(_, _key, default) return default end
|
||||
|
||||
mod.get_option_set = function(_, key, default)
|
||||
-- Populate the sets inspected by is_moderated() with test fixtures.
|
||||
if key == "allowners_moderated_subdomains" then
|
||||
return new_set({ "moderated-subdomain" })
|
||||
elseif key == "allowners_moderated_rooms" then
|
||||
return new_set({ "moderatedroom" })
|
||||
end
|
||||
return new_set(type(default) == 'table' and default or {})
|
||||
end
|
||||
|
||||
mod.get_option_inherited_set = function(_, _key, default)
|
||||
return new_set(type(default) == 'table' and default or {})
|
||||
end
|
||||
|
||||
mod.context = function(_, _host)
|
||||
return {
|
||||
get_option_set = function(_, _key, default)
|
||||
return new_set(type(default) == 'table' and default or {})
|
||||
end,
|
||||
}
|
||||
end
|
||||
|
||||
_G.module = mod
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- Load the module under test
|
||||
-- ---------------------------------------------------------------------------
|
||||
|
||||
local ok, M = pcall(dofile, "util.lib.lua")
|
||||
if not ok then
|
||||
describe("util.lib", function()
|
||||
it("skipped — failed to load", function()
|
||||
pending(tostring(M):match("([^\n]+)") or tostring(M))
|
||||
end)
|
||||
end)
|
||||
return
|
||||
end
|
||||
|
||||
-- Convenience: muc_domain derived from the defaults above
|
||||
local MUC_DOMAIN = "conference.test.example.com"
|
||||
local MUC_BASE = "test.example.com"
|
||||
local MUC_PREFIX = "conference"
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- Tests
|
||||
-- ---------------------------------------------------------------------------
|
||||
|
||||
describe("util.lib", function()
|
||||
|
||||
-- -----------------------------------------------------------------------
|
||||
describe("starts_with", function()
|
||||
it("returns false for nil string", function()
|
||||
assert.is_false(M.starts_with(nil, "x"))
|
||||
end)
|
||||
|
||||
it("returns true when string starts with prefix", function()
|
||||
assert.is_true(M.starts_with("hello world", "hello"))
|
||||
end)
|
||||
|
||||
it("returns false when string does not start with prefix", function()
|
||||
assert.is_false(M.starts_with("hello", "world"))
|
||||
end)
|
||||
|
||||
it("returns true for empty prefix", function()
|
||||
assert.is_true(M.starts_with("hello", ""))
|
||||
end)
|
||||
|
||||
it("returns true when string equals prefix exactly", function()
|
||||
assert.is_true(M.starts_with("abc", "abc"))
|
||||
end)
|
||||
|
||||
it("returns false when prefix is longer than string", function()
|
||||
assert.is_false(M.starts_with("hi", "hello"))
|
||||
end)
|
||||
end)
|
||||
|
||||
-- -----------------------------------------------------------------------
|
||||
describe("ends_with", function()
|
||||
it("returns false for nil string", function()
|
||||
assert.is_false(M.ends_with(nil, "x"))
|
||||
end)
|
||||
|
||||
it("returns true when string ends with suffix", function()
|
||||
assert.is_true(M.ends_with("hello", "llo"))
|
||||
end)
|
||||
|
||||
it("returns false when string does not end with suffix", function()
|
||||
assert.is_false(M.ends_with("hello", "world"))
|
||||
end)
|
||||
|
||||
it("returns true for empty suffix", function()
|
||||
assert.is_true(M.ends_with("hello", ""))
|
||||
end)
|
||||
|
||||
it("returns true when string equals suffix exactly", function()
|
||||
assert.is_true(M.ends_with("abc", "abc"))
|
||||
end)
|
||||
end)
|
||||
|
||||
-- -----------------------------------------------------------------------
|
||||
describe("starts_with_one_of", function()
|
||||
it("returns false for nil string", function()
|
||||
assert.is_false(M.starts_with_one_of(nil, { "a" }))
|
||||
end)
|
||||
|
||||
it("returns false for nil prefixes", function()
|
||||
assert.is_false(M.starts_with_one_of("hello", nil))
|
||||
end)
|
||||
|
||||
it("matches against a plain table of prefixes", function()
|
||||
assert.is_truthy(M.starts_with_one_of("recorder@example.com", { "recorder@", "jibri@" }))
|
||||
end)
|
||||
|
||||
it("returns false when no prefix in table matches", function()
|
||||
assert.is_false(M.starts_with_one_of("user@example.com", { "recorder@", "jibri@" }))
|
||||
end)
|
||||
|
||||
it("matches against a Set of prefixes", function()
|
||||
local prefixes = new_set({ "recorder@", "jibri@" })
|
||||
assert.is_truthy(M.starts_with_one_of("recorder@example.com", prefixes))
|
||||
end)
|
||||
|
||||
it("returns false when no prefix in Set matches", function()
|
||||
local prefixes = new_set({ "recorder@", "jibri@" })
|
||||
assert.is_false(M.starts_with_one_of("user@example.com", prefixes))
|
||||
end)
|
||||
|
||||
it("returns the matched prefix value (table)", function()
|
||||
assert.equal("rec@", M.starts_with_one_of("rec@host", { "rec@", "other@" }))
|
||||
end)
|
||||
end)
|
||||
|
||||
-- -----------------------------------------------------------------------
|
||||
describe("is_healthcheck_room", function()
|
||||
it("recognises a jicofo health-check room", function()
|
||||
assert.is_true(M.is_healthcheck_room("__jicofo-health-check-abc@conference.example.com"))
|
||||
end)
|
||||
|
||||
it("recognises the bare prefix itself", function()
|
||||
assert.is_true(M.is_healthcheck_room("__jicofo-health-check"))
|
||||
end)
|
||||
|
||||
it("returns false for a regular room", function()
|
||||
assert.is_false(M.is_healthcheck_room("myroom@conference.example.com"))
|
||||
end)
|
||||
|
||||
it("returns false for nil", function()
|
||||
assert.is_false(M.is_healthcheck_room(nil))
|
||||
end)
|
||||
end)
|
||||
|
||||
-- -----------------------------------------------------------------------
|
||||
describe("is_feature_allowed", function()
|
||||
it("returns is_moderator when features table is absent", function()
|
||||
assert.is_true(M.is_feature_allowed("feature", nil, true))
|
||||
assert.is_false(M.is_feature_allowed("feature", nil, false))
|
||||
end)
|
||||
|
||||
it("returns true when feature value is the string 'true'", function()
|
||||
assert.is_true(M.is_feature_allowed("f", { f = "true" }, false))
|
||||
end)
|
||||
|
||||
it("returns true when feature value is boolean true", function()
|
||||
assert.is_true(M.is_feature_allowed("f", { f = true }, false))
|
||||
end)
|
||||
|
||||
it("returns false when feature is present but set to false", function()
|
||||
assert.is_false(M.is_feature_allowed("f", { f = false }, true))
|
||||
end)
|
||||
|
||||
it("returns false when feature is absent from table", function()
|
||||
assert.is_false(M.is_feature_allowed("f", { g = "true" }, false))
|
||||
end)
|
||||
end)
|
||||
|
||||
-- -----------------------------------------------------------------------
|
||||
describe("extract_subdomain", function()
|
||||
it("returns nil subdomain and the node itself when no brackets", function()
|
||||
local subdomain, room, customer_id = M.extract_subdomain("myroom")
|
||||
assert.is_nil(subdomain)
|
||||
assert.equal("myroom", room)
|
||||
assert.is_nil(customer_id)
|
||||
end)
|
||||
|
||||
it("extracts subdomain and room name from [sub]room format", function()
|
||||
local subdomain, room = M.extract_subdomain("[foo]myroom")
|
||||
assert.equal("foo", subdomain)
|
||||
assert.equal("myroom", room)
|
||||
end)
|
||||
|
||||
it("extracts subdomain from vpaas magic cookie node", function()
|
||||
local subdomain, room, customer_id =
|
||||
M.extract_subdomain("[vpaas-magic-cookie-abc123]myroom")
|
||||
assert.equal("vpaas-magic-cookie-abc123", subdomain)
|
||||
assert.equal("myroom", room)
|
||||
assert.equal("abc123", customer_id)
|
||||
end)
|
||||
|
||||
it("returns no customer_id for non-vpaas subdomain", function()
|
||||
local subdomain, room, customer_id = M.extract_subdomain("[tenant]myroom")
|
||||
assert.equal("tenant", subdomain)
|
||||
assert.equal("myroom", room)
|
||||
assert.is_nil(customer_id)
|
||||
end)
|
||||
|
||||
it("returns nil subdomain for empty brackets (pattern requires >=1 char)", function()
|
||||
-- "[]room" does not match ^%[([^%]]+)%](.+)$
|
||||
local subdomain, room = M.extract_subdomain("[]room")
|
||||
assert.is_nil(subdomain)
|
||||
assert.equal("[]room", room)
|
||||
end)
|
||||
end)
|
||||
|
||||
-- -----------------------------------------------------------------------
|
||||
-- room_jid_split_subdomain / room_jid_match_rewrite /
|
||||
-- internal_room_jid_match_rewrite
|
||||
--
|
||||
-- With the default stubs:
|
||||
-- muc_domain_prefix = "conference"
|
||||
-- muc_domain_base = "test.example.com"
|
||||
-- target_subdomain_pattern matches conference.<sub>.test.example.com
|
||||
-- -----------------------------------------------------------------------
|
||||
describe("room_jid_split_subdomain", function()
|
||||
it("extracts node, host, resource and subdomain from a multidomain JID", function()
|
||||
local node, host, resource, subdomain =
|
||||
M.room_jid_split_subdomain("room1@conference.foo.test.example.com/res")
|
||||
assert.equal("room1", node)
|
||||
assert.equal("conference.foo.test.example.com", host)
|
||||
assert.equal("res", resource)
|
||||
assert.equal("foo", subdomain)
|
||||
end)
|
||||
|
||||
it("returns nil subdomain for a non-multidomain MUC JID", function()
|
||||
local node, host, _, subdomain =
|
||||
M.room_jid_split_subdomain("room1@conference.test.example.com")
|
||||
assert.equal("room1", node)
|
||||
assert.equal("conference.test.example.com", host)
|
||||
assert.is_nil(subdomain)
|
||||
end)
|
||||
|
||||
it("handles JID with no node (bare host)", function()
|
||||
local node, host, _, subdomain =
|
||||
M.room_jid_split_subdomain("conference.foo.test.example.com")
|
||||
assert.is_nil(node)
|
||||
assert.equal("conference.foo.test.example.com", host)
|
||||
assert.equal("foo", subdomain)
|
||||
end)
|
||||
|
||||
-- Regression: target_subdomain_pattern used an unescaped '.' between the
|
||||
-- prefix and the subdomain capture group. In Lua patterns '.' matches any
|
||||
-- character, so "conference-foo.test.example.com" (hyphen) was incorrectly
|
||||
-- treated as a subdomain JID with subdomain "foo". The correct pattern
|
||||
-- requires a literal dot: "conference%.foo.test.example.com".
|
||||
it("returns nil subdomain for a hyphenated-prefix host (regression: unescaped dot)", function()
|
||||
-- 'conference-foo' shares the same base domain but is NOT a subdomain
|
||||
-- MUC component; the separator is a hyphen, not a dot.
|
||||
local _, _, _, subdomain =
|
||||
M.room_jid_split_subdomain("room@conference-foo.test.example.com")
|
||||
assert.is_nil(subdomain)
|
||||
end)
|
||||
|
||||
it("returns nil subdomain when only the suffix differs by a non-dot character", function()
|
||||
-- e.g. conference.foo-test.example.com — the subdomain capture is
|
||||
-- [^%.]+, so a hyphen inside 'foo-test' is fine, but this test
|
||||
-- confirms the prefix boundary is strict.
|
||||
local _, _, _, subdomain =
|
||||
M.room_jid_split_subdomain("room@conferenceXfoo.test.example.com")
|
||||
assert.is_nil(subdomain)
|
||||
end)
|
||||
end)
|
||||
|
||||
-- -----------------------------------------------------------------------
|
||||
describe("room_jid_match_rewrite", function()
|
||||
it("rewrites a multidomain JID to internal [sub]room@muc_domain form", function()
|
||||
local result = M.room_jid_match_rewrite("room1@conference.foo.test.example.com")
|
||||
assert.equal("[foo]room1@" .. MUC_DOMAIN, result)
|
||||
end)
|
||||
|
||||
it("preserves the resource in the rewritten JID", function()
|
||||
local result = M.room_jid_match_rewrite("room1@conference.foo.test.example.com/r")
|
||||
assert.equal("[foo]room1@" .. MUC_DOMAIN .. "/r", result)
|
||||
end)
|
||||
|
||||
it("returns the JID unchanged when there is no subdomain", function()
|
||||
local jid = "room1@" .. MUC_DOMAIN
|
||||
assert.equal(jid, M.room_jid_match_rewrite(jid))
|
||||
end)
|
||||
|
||||
it("handles host-only JID (no node) — stores id in roomless_iqs and rewrites host", function()
|
||||
local stanza = { attr = { id = "iq-roomless-1", to = "conference.foo.test.example.com" } }
|
||||
local result = M.room_jid_match_rewrite("conference.foo.test.example.com", stanza)
|
||||
-- No node → only host is rewritten
|
||||
assert.equal(MUC_DOMAIN, result)
|
||||
end)
|
||||
|
||||
-- Regression: unescaped '.' in target_subdomain_pattern caused
|
||||
-- "conference-internal.test.example.com" to be treated as a subdomain
|
||||
-- MUC host and rewritten to "[internal]room@conference.test.example.com".
|
||||
it("does not rewrite a JID whose host uses a hyphen after the prefix (regression)", function()
|
||||
local jid = "room@conference-internal.test.example.com"
|
||||
assert.equal(jid, M.room_jid_match_rewrite(jid))
|
||||
end)
|
||||
|
||||
it("does not rewrite a JID whose host uses an arbitrary non-dot separator after the prefix", function()
|
||||
local jid = "room@conferenceXinternal.test.example.com"
|
||||
assert.equal(jid, M.room_jid_match_rewrite(jid))
|
||||
end)
|
||||
end)
|
||||
|
||||
-- -----------------------------------------------------------------------
|
||||
describe("internal_room_jid_match_rewrite", function()
|
||||
it("rewrites an internal [sub]room JID back to pretty conference.sub.base/room form", function()
|
||||
local result = M.internal_room_jid_match_rewrite("[foo]room1@" .. MUC_DOMAIN)
|
||||
assert.equal("room1@" .. MUC_PREFIX .. ".foo." .. MUC_BASE, result)
|
||||
end)
|
||||
|
||||
it("preserves resource in the rewritten JID", function()
|
||||
local result = M.internal_room_jid_match_rewrite("[foo]room1@" .. MUC_DOMAIN .. "/res")
|
||||
assert.equal("room1@" .. MUC_PREFIX .. ".foo." .. MUC_BASE .. "/res", result)
|
||||
end)
|
||||
|
||||
it("returns JID unchanged when host is not the MUC domain", function()
|
||||
local jid = "room1@other.domain.com"
|
||||
assert.equal(jid, M.internal_room_jid_match_rewrite(jid))
|
||||
end)
|
||||
|
||||
it("returns JID unchanged when node lacks [sub] prefix", function()
|
||||
local jid = "room1@" .. MUC_DOMAIN
|
||||
assert.equal(jid, M.internal_room_jid_match_rewrite(jid))
|
||||
end)
|
||||
|
||||
it("resolves a roomless IQ stored by room_jid_match_rewrite", function()
|
||||
local stanza = { attr = { id = "iq-roomless-2", to = "conference.bar.test.example.com" } }
|
||||
-- Trigger storage of the roomless IQ
|
||||
M.room_jid_match_rewrite("conference.bar.test.example.com", stanza)
|
||||
-- internal_room_jid_match_rewrite should return the stored value
|
||||
local result = M.internal_room_jid_match_rewrite("anything@" .. MUC_DOMAIN, stanza)
|
||||
assert.equal("conference.bar.test.example.com", result)
|
||||
end)
|
||||
end)
|
||||
|
||||
-- -----------------------------------------------------------------------
|
||||
describe("table helpers", function()
|
||||
|
||||
describe("table_shallow_copy", function()
|
||||
it("copies all key-value pairs", function()
|
||||
local orig = { a = 1, b = "x", c = true }
|
||||
local copy = M.table_shallow_copy(orig)
|
||||
assert.equal(1, copy.a)
|
||||
assert.equal("x", copy.b)
|
||||
assert.equal(true, copy.c)
|
||||
end)
|
||||
|
||||
it("returns a distinct table (not the same reference)", function()
|
||||
local orig = { x = 1 }
|
||||
local copy = M.table_shallow_copy(orig)
|
||||
assert.not_equal(orig, copy)
|
||||
end)
|
||||
|
||||
it("does not deep-copy nested tables", function()
|
||||
local inner = { z = 9 }
|
||||
local orig = { a = inner }
|
||||
local copy = M.table_shallow_copy(orig)
|
||||
assert.equal(inner, copy.a) -- same reference
|
||||
end)
|
||||
end)
|
||||
|
||||
-- -------------------------------------------------------------------
|
||||
describe("table_find", function()
|
||||
it("returns the index of a found value", function()
|
||||
assert.equal(2, M.table_find({ "a", "b", "c" }, "b"))
|
||||
end)
|
||||
|
||||
it("returns nil when value is not present", function()
|
||||
assert.is_nil(M.table_find({ "a", "b" }, "z"))
|
||||
end)
|
||||
|
||||
it("returns nil for nil table", function()
|
||||
assert.is_nil(M.table_find(nil, "x"))
|
||||
end)
|
||||
|
||||
it("returns nil when searching for nil", function()
|
||||
assert.is_nil(M.table_find({ "a", "b" }, nil))
|
||||
end)
|
||||
end)
|
||||
|
||||
-- -------------------------------------------------------------------
|
||||
describe("table_add", function()
|
||||
it("appends all values from t2 onto t1", function()
|
||||
local t1 = { "a", "b" }
|
||||
local t2 = { "c", "d" }
|
||||
M.table_add(t1, t2)
|
||||
assert.same({ "a", "b", "c", "d" }, t1)
|
||||
end)
|
||||
|
||||
it("leaves t1 unchanged when t2 is empty", function()
|
||||
local t1 = { "a" }
|
||||
M.table_add(t1, {})
|
||||
assert.same({ "a" }, t1)
|
||||
end)
|
||||
end)
|
||||
|
||||
-- -------------------------------------------------------------------
|
||||
describe("table_compare", function()
|
||||
it("reports removed keys", function()
|
||||
local removed, added, modified = M.table_compare({ a = 1, b = 2 }, { a = 1 })
|
||||
assert.equal(1, #removed)
|
||||
assert.equal("b", removed[1])
|
||||
assert.equal(0, #added)
|
||||
assert.equal(0, #modified)
|
||||
end)
|
||||
|
||||
it("reports added keys", function()
|
||||
local removed, added, modified = M.table_compare({ a = 1 }, { a = 1, b = 2 })
|
||||
assert.equal(0, #removed)
|
||||
assert.equal(1, #added)
|
||||
assert.equal("b", added[1])
|
||||
assert.equal(0, #modified)
|
||||
end)
|
||||
|
||||
it("reports modified values", function()
|
||||
local removed, added, modified = M.table_compare({ a = 1 }, { a = 99 })
|
||||
assert.equal(0, #removed)
|
||||
assert.equal(0, #added)
|
||||
assert.equal(1, #modified)
|
||||
assert.equal("a", modified[1])
|
||||
end)
|
||||
|
||||
it("returns three empty lists for identical tables", function()
|
||||
local removed, added, modified = M.table_compare({ x = 1 }, { x = 1 })
|
||||
assert.equal(0, #removed)
|
||||
assert.equal(0, #added)
|
||||
assert.equal(0, #modified)
|
||||
end)
|
||||
end)
|
||||
|
||||
-- -------------------------------------------------------------------
|
||||
describe("table_equals", function()
|
||||
it("returns true for two identical tables", function()
|
||||
assert.is_true(M.table_equals({ a = 1, b = 2 }, { a = 1, b = 2 }))
|
||||
end)
|
||||
|
||||
it("returns false when values differ", function()
|
||||
assert.is_false(M.table_equals({ a = 1 }, { a = 2 }))
|
||||
end)
|
||||
|
||||
it("returns false when keys differ", function()
|
||||
assert.is_false(M.table_equals({ a = 1 }, { b = 1 }))
|
||||
end)
|
||||
|
||||
it("returns true when both arguments are nil", function()
|
||||
assert.is_true(M.table_equals(nil, nil))
|
||||
end)
|
||||
|
||||
it("returns false when only one argument is nil", function()
|
||||
assert.is_false(M.table_equals(nil, {}))
|
||||
assert.is_false(M.table_equals({}, nil))
|
||||
end)
|
||||
end)
|
||||
|
||||
end) -- table helpers
|
||||
|
||||
-- -----------------------------------------------------------------------
|
||||
describe("split_string", function()
|
||||
it("splits a simple comma-delimited string", function()
|
||||
local result = M.split_string("a,b,c", ",")
|
||||
assert.equal("a", result[1])
|
||||
assert.equal("b", result[2])
|
||||
assert.equal("c", result[3])
|
||||
end)
|
||||
|
||||
it("produces a single element when delimiter is absent", function()
|
||||
local result = M.split_string("hello", ",")
|
||||
assert.equal("hello", result[1])
|
||||
assert.is_nil(result[2])
|
||||
end)
|
||||
|
||||
it("produces empty strings at boundaries", function()
|
||||
local result = M.split_string(",a,", ",")
|
||||
assert.equal("", result[1])
|
||||
assert.equal("a", result[2])
|
||||
assert.equal("", result[3])
|
||||
end)
|
||||
|
||||
it("handles multi-character delimiter", function()
|
||||
local result = M.split_string("x::y::z", "::")
|
||||
assert.equal("x", result[1])
|
||||
assert.equal("y", result[2])
|
||||
assert.equal("z", result[3])
|
||||
end)
|
||||
end)
|
||||
|
||||
-- -----------------------------------------------------------------------
|
||||
describe("get_sip_jibri_email_prefix", function()
|
||||
it("returns nil for nil email", function()
|
||||
assert.is_nil(M.get_sip_jibri_email_prefix(nil))
|
||||
end)
|
||||
|
||||
it("returns nil for unrecognised email", function()
|
||||
assert.is_nil(M.get_sip_jibri_email_prefix("user@example.com"))
|
||||
end)
|
||||
|
||||
it("recognises outbound-sip-jibri@ prefix", function()
|
||||
assert.equal("outbound-sip-jibri@",
|
||||
M.get_sip_jibri_email_prefix("outbound-sip-jibri@example.com"))
|
||||
end)
|
||||
|
||||
it("recognises sipjibriouta@ prefix", function()
|
||||
assert.equal("sipjibriouta@",
|
||||
M.get_sip_jibri_email_prefix("sipjibriouta@example.com"))
|
||||
end)
|
||||
|
||||
it("recognises sipjibrioutb@ prefix", function()
|
||||
assert.equal("sipjibrioutb@",
|
||||
M.get_sip_jibri_email_prefix("sipjibrioutb@example.com"))
|
||||
end)
|
||||
|
||||
it("recognises inbound-sip-jibri@ prefix", function()
|
||||
assert.equal("inbound-sip-jibri@",
|
||||
M.get_sip_jibri_email_prefix("inbound-sip-jibri@example.com"))
|
||||
end)
|
||||
|
||||
it("recognises sipjibriina@ prefix", function()
|
||||
assert.equal("sipjibriina@",
|
||||
M.get_sip_jibri_email_prefix("sipjibriina@example.com"))
|
||||
end)
|
||||
end)
|
||||
|
||||
-- -----------------------------------------------------------------------
|
||||
describe("is_jibri", function()
|
||||
it("returns truthy for recorder@ prefixed JID string", function()
|
||||
assert.is_truthy(M.is_jibri("recorder@recorder.example.com"))
|
||||
end)
|
||||
|
||||
it("returns truthy for jibria@ prefixed JID string", function()
|
||||
assert.is_truthy(M.is_jibri("jibria@recorder.example.com"))
|
||||
end)
|
||||
|
||||
it("returns truthy for jibrib@ prefixed JID string", function()
|
||||
assert.is_truthy(M.is_jibri("jibrib@recorder.example.com"))
|
||||
end)
|
||||
|
||||
it("returns false for a non-recorder JID string", function()
|
||||
assert.is_false(M.is_jibri("user@example.com"))
|
||||
end)
|
||||
|
||||
it("accepts an occupant object and inspects its .jid field", function()
|
||||
local occupant = { jid = "recorder@recorder.example.com" }
|
||||
assert.is_truthy(M.is_jibri(occupant))
|
||||
end)
|
||||
|
||||
it("returns false for occupant object with non-recorder JID", function()
|
||||
local occupant = { jid = "user@example.com" }
|
||||
assert.is_false(M.is_jibri(occupant))
|
||||
end)
|
||||
end)
|
||||
|
||||
-- -----------------------------------------------------------------------
|
||||
describe("is_transcriber", function()
|
||||
it("returns truthy for transcriber@ JID", function()
|
||||
assert.is_truthy(M.is_transcriber("transcriber@recorder.example.com"))
|
||||
end)
|
||||
|
||||
it("returns truthy for transcribera@ JID", function()
|
||||
assert.is_truthy(M.is_transcriber("transcribera@recorder.example.com"))
|
||||
end)
|
||||
|
||||
it("returns truthy for transcriberb@ JID", function()
|
||||
assert.is_truthy(M.is_transcriber("transcriberb@recorder.example.com"))
|
||||
end)
|
||||
|
||||
it("returns false for a regular user JID", function()
|
||||
assert.is_false(M.is_transcriber("user@example.com"))
|
||||
end)
|
||||
|
||||
it("returns false for nil", function()
|
||||
assert.is_false(M.is_transcriber(nil))
|
||||
end)
|
||||
end)
|
||||
|
||||
-- -----------------------------------------------------------------------
|
||||
describe("is_vpaas", function()
|
||||
-- muc_domain = "conference.test.example.com" (derived from defaults)
|
||||
|
||||
it("returns false for nil room", function()
|
||||
assert.is_false(M.is_vpaas(nil))
|
||||
end)
|
||||
|
||||
it("returns true for a room whose tenant starts with vpaas-magic-cookie-", function()
|
||||
local room = { jid = "[vpaas-magic-cookie-xyz]myroom@" .. MUC_DOMAIN }
|
||||
assert.is_true(M.is_vpaas(room))
|
||||
end)
|
||||
|
||||
it("caches the result in room.is_vpaas", function()
|
||||
local room = { jid = "[vpaas-magic-cookie-xyz]myroom@" .. MUC_DOMAIN }
|
||||
M.is_vpaas(room)
|
||||
assert.is_true(room.is_vpaas)
|
||||
end)
|
||||
|
||||
it("returns false for a room on the wrong MUC domain", function()
|
||||
local room = { jid = "[vpaas-magic-cookie-xyz]myroom@other.domain.com" }
|
||||
assert.is_false(M.is_vpaas(room))
|
||||
end)
|
||||
|
||||
it("returns false for a room without a tenant bracket prefix", function()
|
||||
local room = { jid = "myroom@" .. MUC_DOMAIN }
|
||||
assert.is_false(M.is_vpaas(room))
|
||||
end)
|
||||
|
||||
it("returns false for a room whose tenant does not start with vpaas-magic-cookie-", function()
|
||||
local room = { jid = "[othertenant]myroom@" .. MUC_DOMAIN }
|
||||
assert.is_false(M.is_vpaas(room))
|
||||
end)
|
||||
|
||||
it("returns cached is_vpaas value when present", function()
|
||||
local room = { jid = "[vpaas-magic-cookie-xyz]myroom@" .. MUC_DOMAIN, is_vpaas = false }
|
||||
-- Cached false should be returned without re-evaluating the JID
|
||||
assert.is_false(M.is_vpaas(room))
|
||||
end)
|
||||
end)
|
||||
|
||||
-- -----------------------------------------------------------------------
|
||||
describe("is_moderated", function()
|
||||
-- moderated_subdomains = {"moderated-subdomain"} (configured in stub)
|
||||
-- moderated_rooms = {"moderatedroom"}
|
||||
|
||||
it("returns true and room/subdomain for a moderated subdomain", function()
|
||||
local result, room_name, subdomain =
|
||||
M.is_moderated("[moderated-subdomain]someroom@" .. MUC_DOMAIN)
|
||||
assert.is_true(result)
|
||||
assert.equal("someroom", room_name)
|
||||
assert.equal("moderated-subdomain", subdomain)
|
||||
end)
|
||||
|
||||
it("returns true and room name for a moderated room (no subdomain)", function()
|
||||
local result, room_name, subdomain =
|
||||
M.is_moderated("moderatedroom@" .. MUC_DOMAIN)
|
||||
assert.is_true(result)
|
||||
assert.equal("moderatedroom", room_name)
|
||||
assert.is_nil(subdomain)
|
||||
end)
|
||||
|
||||
it("returns false for an unmoderated room with a subdomain", function()
|
||||
assert.is_false(M.is_moderated("[other-subdomain]room@" .. MUC_DOMAIN))
|
||||
end)
|
||||
|
||||
it("returns false for an unmoderated room without a subdomain", function()
|
||||
assert.is_false(M.is_moderated("normalroom@" .. MUC_DOMAIN))
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("is_focus", function()
|
||||
|
||||
it("nick ending in /focus returns true", function()
|
||||
assert.is_true(M.is_focus("room@conference.example.com/focus"))
|
||||
end)
|
||||
|
||||
it("plain /focus string returns true", function()
|
||||
assert.is_true(M.is_focus("/focus"))
|
||||
end)
|
||||
|
||||
it("regular nick returns false", function()
|
||||
assert.is_false(M.is_focus("room@conference.example.com/user1"))
|
||||
end)
|
||||
|
||||
it("nick containing focus but not ending in /focus returns false", function()
|
||||
assert.is_false(M.is_focus("room@conference.example.com/focususer"))
|
||||
end)
|
||||
|
||||
it("bare word focus without slash returns false", function()
|
||||
assert.is_false(M.is_focus("focus"))
|
||||
end)
|
||||
|
||||
it("empty string returns false", function()
|
||||
assert.is_false(M.is_focus(""))
|
||||
end)
|
||||
|
||||
end)
|
||||
|
||||
describe("is_focus_nick", function()
|
||||
|
||||
it("returns true for the bare string 'focus'", function()
|
||||
assert.is_true(M.is_focus_nick("focus"))
|
||||
end)
|
||||
|
||||
it("returns false for a regular nick", function()
|
||||
assert.is_false(M.is_focus_nick("user1"))
|
||||
end)
|
||||
|
||||
it("returns false for a nick that contains focus but is not exactly focus", function()
|
||||
assert.is_false(M.is_focus_nick("focususer"))
|
||||
end)
|
||||
|
||||
it("returns false for a full MUC JID (slash-qualified)", function()
|
||||
assert.is_false(M.is_focus_nick("room@conference.example.com/focus"))
|
||||
end)
|
||||
|
||||
it("returns false for empty string", function()
|
||||
assert.is_false(M.is_focus_nick(""))
|
||||
end)
|
||||
|
||||
end)
|
||||
|
||||
describe("is_focus_jid", function()
|
||||
|
||||
it("returns true for focus@auth.example.com", function()
|
||||
assert.is_true(M.is_focus_jid("focus@auth.example.com"))
|
||||
end)
|
||||
|
||||
it("returns true for focus@auth.example.com/resource", function()
|
||||
assert.is_true(M.is_focus_jid("focus@auth.example.com/res123"))
|
||||
end)
|
||||
|
||||
it("returns false for a regular user JID", function()
|
||||
assert.is_false(M.is_focus_jid("user@auth.example.com"))
|
||||
end)
|
||||
|
||||
it("returns false for JID whose node contains but is not exactly focus", function()
|
||||
assert.is_false(M.is_focus_jid("focususer@auth.example.com"))
|
||||
end)
|
||||
|
||||
it("returns false for a host-only JID (no node)", function()
|
||||
assert.is_false(M.is_focus_jid("auth.example.com"))
|
||||
end)
|
||||
|
||||
it("returns false for nil", function()
|
||||
assert.is_false(M.is_focus_jid(nil))
|
||||
end)
|
||||
|
||||
end)
|
||||
|
||||
describe("build_room_address", function()
|
||||
-- muc_domain_prefix = "conference" (set in module stub above)
|
||||
|
||||
it("builds room JID with no subdomain", function()
|
||||
assert.equal("myroom@conference.example.com",
|
||||
M.build_room_address("myroom", "example.com", nil))
|
||||
end)
|
||||
|
||||
it("prepends [subdomain] when subdomain is given", function()
|
||||
assert.equal("[tenant]myroom@conference.example.com",
|
||||
M.build_room_address("myroom", "example.com", "tenant"))
|
||||
end)
|
||||
|
||||
it("treats empty-string subdomain as no subdomain", function()
|
||||
assert.equal("myroom@conference.example.com",
|
||||
M.build_room_address("myroom", "example.com", ""))
|
||||
end)
|
||||
|
||||
it("uses the configured muc_domain_prefix (conference)", function()
|
||||
assert.equal("room@conference.meet.jit.si",
|
||||
M.build_room_address("room", "meet.jit.si", nil))
|
||||
end)
|
||||
|
||||
it("combines subdomain with multi-part base domain", function()
|
||||
assert.equal("[vpaas-magic-cookie-abc]r@conference.meet.jit.si",
|
||||
M.build_room_address("r", "meet.jit.si", "vpaas-magic-cookie-abc"))
|
||||
end)
|
||||
|
||||
end)
|
||||
|
||||
end) -- util.lib
|
||||
Reference in New Issue
Block a user