File

spec/util_datamapper_spec.lua @ 11590:5aafb832c91b

core.portmanager: Fix race condition in initialization of SNI cert map Under some circumstances when hosts and modules are loaded in some certain order, entries end up missing from the SNI map. This manifests in e.g. `curl https://localhost:5281/` giving an error about "unrecognized name". The `service` argument is `nil` when invoked from the "host-activated" event, leading it to iterating over every service. And then it would not be fetching e.g. `http_host` from the config, which explains why https would sometimes not work due to the missing name entry. Because when `service` is included, this limits the iteration to matching entries, while also returning the same value as the `name` loop variable. Because `name == service when service != nil` we can use name instead in the body of the loop.
author Kim Alvefur <zash@zash.se>
date Fri, 28 May 2021 17:09:22 +0200
parent 11494:284c822d4c3d
child 12133:11060c8919b6
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.datampper", 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 function attr() return {type = "string"; xml = {attribute = true}} end
		s = {
			type = "object";
			xml = {name = "message"; namespace = "jabber:client"};
			properties = {
				to = attr();
				from = attr();
				type = attr();
				id = attr();
				body = "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"}};
						reactions = {type = "array"; items = {type = "string"; 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 = {
			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 = {name = "string"; version = "string"; os = "string"};
					};
				};
			};
			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)