File

spec/util_datamapper_spec.lua @ 13616:2f38f3275a74

mod_cloud_notify: Merge from prosody-modules@fc521fb5ffa0 Many thanks to Thilo Molitor and Kim Alvefur for their work on this module while it was in the community repository. It has been stable for some time, is widely used, and provides a feature that is important to most deployments.
author Matthew Wild <mwild1@gmail.com>
date Thu, 09 Jan 2025 16:49:27 +0000
parent 12818:74ed772ff5fb
line wrap: on
line source

local st
local xml
local map

setup(function()
	st = require "util.stanza";
	xml = require "util.xml";
	map = require "util.datamapper";
end);

describe("util.datamapper", function()

	local s, x, d
	local disco, disco_info, disco_schema
	setup(function()

		-- a convenience function for simple attributes, there's a few of them
		local attr = {["$ref"]="#/$defs/attr"};
		s = {
			["$defs"] = { attr = { type = "string"; xml = { attribute = true } } };
			type = "object";
			xml = {name = "message"; namespace = "jabber:client"};
			properties = {
				to = attr;
				from = attr;
				type = attr;
				id = attr;
				body = true; -- should be assumed to be a string
				lang = {type = "string"; xml = {attribute = true; prefix = "xml"}};
				delay = {
					type = "object";
					xml = {namespace = "urn:xmpp:delay"; name = "delay"};
					properties = {stamp = attr; from = attr; reason = {type = "string"; xml = {text = true}}};
				};
				state = {
					type = "string";
					enum = {
						"active",
						"inactive",
						"gone",
						"composing",
						"paused",
					};
					xml = {x_name_is_value = true; namespace = "http://jabber.org/protocol/chatstates"};
				};
				fallback = {
					type = "boolean";
					xml = {x_name_is_value = true; name = "fallback"; namespace = "urn:xmpp:fallback:0"};
				};
				origin_id = {
					type = "string";
					xml = {name = "origin-id"; namespace = "urn:xmpp:sid:0"; x_single_attribute = "id"};
				};
				react = {
					type = "object";
					xml = {namespace = "urn:xmpp:reactions:0"; name = "reactions"};
					properties = {
						to = {type = "string"; xml = {attribute = true; name = "id"}};
						-- should be assumed to be array since it has 'items'
						reactions = { items = { xml = { name = "reaction" } } };
					};
				};
				stanza_ids = {
					type = "array";
					items = {
						xml = {name = "stanza-id"; namespace = "urn:xmpp:sid:0"};
						type = "object";
						properties = {
							id = attr;
							by = attr;
						};
					};
				};
			};
		};

		x = xml.parse [[
				<message xmlns="jabber:client" xml:lang="en" to="a@test" from="b@test" type="chat" id="1">
				<body>Hello</body>
				<delay xmlns='urn:xmpp:delay' from='test' stamp='2021-03-07T15:59:08+00:00'>Because</delay>
				<UNRELATED xmlns='http://jabber.org/protocol/chatstates'/>
				<active xmlns='http://jabber.org/protocol/chatstates'/>
				<fallback xmlns='urn:xmpp:fallback:0'/>
				<origin-id xmlns='urn:xmpp:sid:0' id='qgkmMdPB'/>
				<stanza-id xmlns='urn:xmpp:sid:0' id='abc1' by='muc'/>
				<stanza-id xmlns='urn:xmpp:sid:0' id='xyz2' by='host'/>
				<reactions id='744f6e18-a57a-11e9-a656-4889e7820c76' xmlns='urn:xmpp:reactions:0'>
					<reaction>👋</reaction>
					<reaction>🐢</reaction>
				</reactions>
				</message>
				]];

		d = {
			to = "a@test";
			from = "b@test";
			type = "chat";
			id = "1";
			lang = "en";
			body = "Hello";
			delay = {from = "test"; stamp = "2021-03-07T15:59:08+00:00"; reason = "Because"};
			state = "active";
			fallback = true;
			origin_id = "qgkmMdPB";
			stanza_ids = {{id = "abc1"; by = "muc"}; {id = "xyz2"; by = "host"}};
			react = {
				to = "744f6e18-a57a-11e9-a656-4889e7820c76";
				reactions = {
					"👋",
					"🐢",
				};
			};
		};

		disco_schema = {
			["$defs"] = { attr = { type = "string"; xml = { attribute = true } } };
			type = "object";
			xml = {
				name = "iq";
				namespace = "jabber:client"
			};
			properties = {
				to = attr;
				from = attr;
				type = attr;
				id = attr;
				disco = {
					type = "object";
					xml = {
						name = "query";
						namespace	= "http://jabber.org/protocol/disco#info"
					};
					properties = {
						features = {
							type = "array";
							items = {
								type = "string";
								xml = {
									name = "feature";
									x_single_attribute = "var";
								};
							};
						};
					};
				};
			};
		};

		disco_info = xml.parse[[
		<iq type="result" id="disco1" from="example.com">
			<query xmlns="http://jabber.org/protocol/disco#info">
				<feature var="urn:example:feature:1">wrong</feature>
				<feature var="urn:example:feature:2"/>
				<feature var="urn:example:feature:3"/>
				<unrelated var="urn:example:feature:not"/>
			</query>
		</iq>
		]];

		disco = {
			type="result";
			id="disco1";
			from="example.com";
			disco = {
				features = {
					"urn:example:feature:1";
					"urn:example:feature:2";
					"urn:example:feature:3";
				};
			};
		};
	end);

	describe("parse", function()
		it("works", function()
			assert.same(d, map.parse(s, x));
		end);

		it("handles arrays", function ()
			assert.same(disco, map.parse(disco_schema, disco_info));
		end);

		it("deals with locally built stanzas", function()
			-- FIXME this could also be argued to be a util.stanza problem
			local ver_schema = {
				type = "object";
				xml = {name = "iq"};
				properties = {
					type = {type = "string"; xml = {attribute = true}};
					id = {type = "string"; xml = {attribute = true}};
					version = {
						type = "object";
						xml = {name = "query"; namespace = "jabber:iq:version"};
						-- properties should be assumed to be strings
						properties = {name = true; version = {}; os = {}};
					};
				};
			};
			local ver_st = st.iq({type = "result"; id = "v1"})
				:query("jabber:iq:version")
					:text_tag("name", "Prosody")
					:text_tag("version", "trunk")
					:text_tag("os", "Lua 5.3")
				:reset();

			local data = {type = "result"; id = "v1"; version = {name = "Prosody"; version = "trunk"; os = "Lua 5.3"}}
			assert.same(data, map.parse(ver_schema, ver_st));
		end);

	end);

	describe("unparse", function()
		it("works", function()
			local u = map.unparse(s, d);
			assert.equal("message", u.name);
			assert.same(x.attr, u.attr);
			assert.equal(x:get_child_text("body"), u:get_child_text("body"));
			assert.equal(x:get_child_text("delay", "urn:xmpp:delay"), u:get_child_text("delay", "urn:xmpp:delay"));
			assert.same(x:get_child("delay", "urn:xmpp:delay").attr, u:get_child("delay", "urn:xmpp:delay").attr);
			assert.same(x:get_child("origin-id", "urn:xmpp:sid:0").attr, u:get_child("origin-id", "urn:xmpp:sid:0").attr);
			assert.same(x:get_child("reactions", "urn:xmpp:reactions:0").attr, u:get_child("reactions", "urn:xmpp:reactions:0").attr);
			assert.same(2, #u:get_child("reactions", "urn:xmpp:reactions:0").tags);
			for _, tag in ipairs(x.tags) do
				if tag.name ~= "UNRELATED" then
					assert.truthy(u:get_child(tag.name, tag.attr.xmlns) or u:get_child(tag.name), tag:top_tag())
				end
			end
			assert.equal(#x.tags-1, #u.tags)

		end);

		it("handles arrays", function ()
			local u = map.unparse(disco_schema, disco);
			assert.equal("urn:example:feature:1", u:find("{http://jabber.org/protocol/disco#info}query/feature/@var"))
			local n = 0;
			for child in u:get_child("query", "http://jabber.org/protocol/disco#info"):childtags("feature") do
				n = n + 1;
				assert.equal(string.format("urn:example:feature:%d", n), child.attr.var);
			end
		end);

	end);
end)