Jump to content

Module:Mock title/testcases

From Wikipedia, the free encyclopedia
This is an old revision of this page, as edited by Mr. Stradivarius (talk | contribs) at 13:08, 20 August 2023 (add test for clearMockTitleRegistry). The present address (URL) is a permanent link to this revision, which may differ significantly from the current revision.
local mMockTitle = require("Module:Mock title")
local ScribuntoUnit = require("Module:ScribuntoUnit")

local suite = ScribuntoUnit:new()

--------------------------------------------------------------------------------
-- Custom assertion methods
--------------------------------------------------------------------------------

function suite:assertTitlesAreEqual(expected, actual)
	local properties = {
		"prefixedText",
		"id",
		"exists",
		"isRedirect",
		"redirectTarget",
		"contentModel",
	}
	for _, prop in ipairs(properties) do
		self:assertEquals(
			expected[prop],
			actual[prop],
			string.format('mismatch in property "%s"', prop)
		)
	end
	
	local deepProperties = {"protectionLevels", "cascadingProtection"}
	for _, prop in ipairs(deepProperties) do
		self:assertDeepEquals(
			expected[prop],
			actual[prop],
			string.format('mismatch in property "%s"', prop)
		)
	end
	
	self:assertEquals(
		not not expected.file,
		not not actual.file,
		string.format(
			'expected %s file property; actual %s file property',
			expected.file and 'has' or 'has no',
			actual.file and 'has' or 'has no'
		)
	)
	
	if expected.file and actual.file then
		local fileProperties = {
			"exists",
			"width",
			"height",
			"size",
			"mimeType",
			"length",
		}
		for _, prop in ipairs(fileProperties) do
			self:assertEquals(
				expected.file[prop],
				actual.file[prop],
				string.format('mismatch in property "file.%s"', prop)
			)
		end
		
		self:assertDeepEquals(
			expected.file.pages,
			actual.file.pages,
			string.format('mismatch in property "%s"', prop)
		)
	end
	
	self:assertEquals(
		expected:getContent(),
		actual:getContent(),
		"mismatch in getContent()"
	)
end

function suite:assertTitlesAreNotEqual(expected, actual)
	local success, result = pcall(self.assertTitlesAreEqual, self, expected, actual)
	if success then
		self:fail(string.format(
			'Failed to assert that the specified titles are not equal (title: %s)',
			tostring(expected)
		))
	end
end

--------------------------------------------------------------------------------
-- MockTitle tests
--------------------------------------------------------------------------------

suite["test MockTitle: when no argument table is supplied, an error is raised"] = function (self)
	self:assertThrows(
		function () mMockTitle.MockTitle() end,
		"bad argument #1 to 'MockTitle' (table expected, got nil)"
	)
end

suite["test MockTitle: when no title argument is supplied, an error is raised"] = function (self)
	self:assertThrows(
		function () mMockTitle.MockTitle{} end,
		"bad named argument title to 'MockTitle' (string or number expected, got nil)"
	)
end

local pageNames = {
	"Example",
	"fr:Example",
	"Module:Sandbox",
	"Module:Sandbox/subpage",
	"mw:Test",
	"fr:b:Example",
}

for _, pageName in ipairs(pageNames) do
	suite[
		string.format(
			'test MockTitle: when using page name "%s", the prefixedText property equals the page name',
			pageName
		)
	] = function (self)
		self:assertEquals(pageName, mMockTitle.MockTitle({title = pageName}).prefixedText)
	end
end

local simplePropertyTestData = {
	{
		property = "id",
		title = "Example",
		value = 123456,
	},
	{
		property = "isRedirect",
		title = "Main Page",
		value = true,
	},
	{
		property = "exists",
		title = "Main Page",
		value = false,
	},
	{
		property = "contentModel",
		title = "Main Page",
		value = "foo",
	},
}

for _, testData in ipairs(simplePropertyTestData) do
	suite[string.format("test MockTitle: property %s is mocked using the %s option", testData.property, testData.property)] = function (self)
		local title = mMockTitle.MockTitle({title = testData.title, [testData.property] = testData.value})
		self:assertEquals(testData.value, title[testData.property])
	end
end

suite["test MockTitle: passing a page name to the redirectTarget option causes redirectTarget to be a MockTitle object with that page name"] = function (self)
	local title = mMockTitle.MockTitle({title = "Example", redirectTarget = "User:Example"})
	self:assertEquals("User:Example", title.redirectTarget.prefixedText)
end

suite["test MockTitle: passing an ID to the redirectTarget option causes redirectTarget to be a MockTitle object with that ID"] = function (self)
	local mainPageId = 15580374
	local title = mMockTitle.MockTitle({title = "Example", redirectTarget = mainPageId})
	self:assertEquals(mainPageId, title.redirectTarget.id)
end

suite["test MockTitle: passing an options table to the redirectTarget option causes redirectTarget to be a MockTitle object with those options"] = function (self)
	local title = mMockTitle.MockTitle({title = "Example", redirectTarget = {title = "User:Example/common.js", contentModel = "wikitext"}})
	self:assertEquals("User:Example/common.js", title.redirectTarget.prefixedText)
	self:assertEquals("wikitext", title.redirectTarget.contentModel)
end

suite["test MockTitle: passing a MockTitle object to the redirectTarget option causes redirectTarget to be that MockTitle object"] = function (self)
	local mockRedirectTarget = mMockTitle.MockTitle({title = "User:Example/common.js", contentModel = "wikitext"})
	local title = mMockTitle.MockTitle({title = "Example", redirectTarget = mockRedirectTarget})
	self:assertEquals("User:Example/common.js", title.redirectTarget.prefixedText)
	self:assertEquals("wikitext", title.redirectTarget.contentModel)
end

local protectionLevelTestData = {
	{
		optionName = "editProtection",
		optionValue = "sysop",
		expectedProtectionAction = "edit",
	},
	{
		optionName = "moveProtection",
		optionValue = "sysop",
		expectedProtectionAction = "move",
	},
	{
		optionName = "createProtection",
		optionValue = "sysop",
		expectedProtectionAction = "create",
	},
	{
		optionName = "uploadProtection",
		optionValue = "sysop",
		expectedProtectionAction = "upload",
	},
}

for _, testData in ipairs(protectionLevelTestData) do
	suite[
		string.format(
			'test MockTitle: when setting option %s to "%s", the protectionLevels table is updated accordingly',
			testData.optionName,
			testData.optionValue
		)
	] = function (self)
		local title = mMockTitle.MockTitle{title = "Example", [testData.optionName] = testData.optionValue}
		self:assertEquals(testData.optionValue, title.protectionLevels[testData.expectedProtectionAction][1])
	end
end

local cascadingProtectionLevelTestData = {
	{
		optionName = "cascadingEditProtection",
		optionValue = "sysop",
		expectedProtectionAction = "edit",
	},
	{
		optionName = "cascadingMoveProtection",
		optionValue = "sysop",
		expectedProtectionAction = "move",
	},
	{
		optionName = "cascadingCreateProtection",
		optionValue = "sysop",
		expectedProtectionAction = "create",
	},
	{
		optionName = "cascadingUploadProtection",
		optionValue = "sysop",
		expectedProtectionAction = "upload",
	},
}

for _, testData in ipairs(cascadingProtectionLevelTestData) do
	suite[
		string.format(
			'test MockTitle: when setting option %s to "%s", '
			.. 'and when setting the cascadingProtectionSources option, '
			.. 'the cascadingProtection table is updated accordingly',
			testData.optionName,
			testData.optionValue
		)
	] = function (self)
		local title = mMockTitle.MockTitle{
			title = "Example",
			[testData.optionName] = testData.optionValue,
			cascadingProtectionSources = {"Example", "Example 2"},
		}
		self:assertEquals(
			testData.optionValue,
			title.cascadingProtection.restrictions[testData.expectedProtectionAction][1]
		)
		self:assertDeepEquals(
			{"Example", "Example 2"},
			title.cascadingProtection.sources
		)
	end
end

suite["test MockTitle: when a cascading protection argument is given, but no cascading protection sources are given, an error is raised"] = function (self)
	self:assertThrows(
		function () mMockTitle.MockTitle{title = "Example", cascadingEditProtection = "sysop"} end,
		"a cascading protection argument was given but the cascadingProtectionSources argument was missing or empty"
	)
end

suite["test MockTitle: when a cascading protection argument is given, but the cascading protection sources table is empty, an error is raised"] = function (self)
	self:assertThrows(
		function () mMockTitle.MockTitle{title = "Example", cascadingEditProtection = "sysop", cascadingProtectionSources = {}} end,
		"a cascading protection argument was given but the cascadingProtectionSources argument was missing or empty"
	)
end

suite["test MockTitle: when cascading protection sources are given, but no cascading protection argument is given, an error is raised"] = function (self)
	self:assertThrows(
		function () mMockTitle.MockTitle{title = "Example", cascadingProtectionSources = {"Example 2", "Example 3"}} end,
		"the cascadingProtectionSources argument was given, but no cascading protection argument was given"
	)
end

suite['test MockTitle: if the content option is provided, getContent() returns the specified string'] = function (self)
	local title = mMockTitle.MockTitle{title = "Non-existent page 29dh12yxm", content = "some [[wikitext]] content"}
	self:assertEquals("some [[wikitext]] content", title:getContent())
end

local filePropertyTestData = {
	{
		property = "exists",
		optionName = "fileExists",
		optionValue = true,
	},
	{
		property = "width",
		optionName = "fileWidth",
		optionValue = 123,
	},
	{
		property = "height",
		optionName = "fileHeight",
		optionValue = 456,
	},
	{
		property = "pages",
		optionName = "filePages",
		optionValue =  {{height = 800, width = 600}, {height = 800, width = 600}},
	},
	{
		property = "size",
		optionName = "fileSize",
		optionValue = 1024,
	},
	{
		property = "mimeType",
		optionName = "fileMimeType",
		optionValue = "image/png",
	},
	{
		property = "length",
		optionName = "fileLength",
		optionValue = 60,
	},
}

for _, testData in ipairs(filePropertyTestData) do
	suite[string.format(
			'test MockTitle: when setting option %s to "%s", the file table is updated accordingly',
			testData.optionName,
			tostring(testData.optionValue)
	)] = function(self)
		local title = mMockTitle.MockTitle{title = "File:Non-existent hf893bc0.png", [testData.optionName] = testData.optionValue}
		self:assertEquals(testData.optionValue, title.file[testData.property])
	end
end

suite["test MockTitle: when setting fileExists in a non-file namespace, no file table is set"] = function(self)
	local title = mMockTitle.MockTitle{title = "Non-existent page 34u8wg90bfr", fileExists = true}
	self:assertEquals(nil, title.file)
end

local fileExistsTestData = {
	{
		title = "Non-existent article tr9w78ebna0",
		expected = nil,
	},
	{
		title = "Talk:Non-existent talk page 34hdbe0pafj",
		expected = nil,
	},
	{
		title = 	"File:Non-existent file 341gh87fgg8",
		expected = true,
	},
	{
		title = "Media:Non-existent file pbfhrw3v8d",
		expected = true,
	},
}

for _, testData in ipairs(fileExistsTestData) do
	suite[
		string.format(
			'test MockTitle: when using page "%s", the fileExists property is %s',
			testData.title,
			testData.expected and "set" or "not set"
		)
	] = function (self)
		local title = mMockTitle.MockTitle({title = testData.title, fileExists = true})
		self:assertEquals(testData.expected, title.fileExists)
	end
end

local fallbackPropertyTestData = {
	{
		property = "id",
		option = "id",
		title = "Example",
	},
	{
		property = "isRedirect",
		option = "isRedirect",
		title = "WP:ANI",
	},
	{
		property = "exists",
		option = "exists",
		title = "Non-existent title f292umz0tyi",
	},
	{
		property = "contentModel",
		option = "contentModel",
		title = "User:Example/common.js",
	},
	{
		property = "redirectTarget",
		option = "redirectTarget",
		title = "WP:ANI",
	},
	{
		property = "protectionLevels",
		option = "editProtection",
		title = "Main Page",
	},
	{
		property = "redirectTarget",
		option = "redirectTarget",
		title = "WP:ANI",
	},
	{
		property = "cascadingProtection",
		option = "cascadingEditProtection",
		title = "Main Page",
	},
	{
		property = "cascadingProtection",
		option = "cascadingProtectionSources",
		title = "Main Page",
	},
}

for _, testData in ipairs(fallbackPropertyTestData) do
	suite[string.format("test MockTitle: if no %s option is specified, the real %s value is used", testData.option, testData.property)] = function (self)
		suite:assertDeepEquals(
			mw.title.new(testData.title)[testData.property],
			mMockTitle.MockTitle{title = testData.title}[testData.property]
		)
	end
end

suite["test MockTitle: if no content option is specified, getContent() returns the the real content"] = function (self)
	suite:assertEquals(
		mw.title.new("Main Page"):getContent(),
		mMockTitle.MockTitle{title = "Main Page"}:getContent()
	)
end

suite["test MockTitle: the content property is not shared between mock objects"] = function (self)
	suite:assertNotEquals(
		mMockTitle.MockTitle{title = "Foo", content = "Bar"}:getContent(),
		mMockTitle.MockTitle{title = "Foo", content = "Baz"}:getContent()
	)
end

--------------------------------------------------------------------------------
-- patchTitleObject tests
--------------------------------------------------------------------------------

suite["test patchTitleObject: patchTitleObject returns an equivalent object to MockTitle when used on the same title"] = function (self)
	suite:assertTitlesAreEqual(
		mMockTitle.MockTitle{
			title = "Main Page",
			isRedirect = true,
			redirectTarget = "Wikipedia:Village stocks",
			content = '#REDIRECT: [[Wikipedia:Village stocks]]',
		},
		mMockTitle.patchTitleObject(
			mw.title.new('Main Page'),
			{
				isRedirect = true,
				redirectTarget = "Wikipedia:Village stocks",
				content = "#REDIRECT: [[Wikipedia:Village stocks]]",
			}
		)
	)
end

suite["test patchTitleObject: title arguments are ignored"] = function (self)
	self:assertEquals(
		'User:Example',
		mMockTitle.patchTitleObject(
			mw.title.new('User:Example'),
			{title = "Wikipedia:Sandbox"}
		).prefixedText
	)
end

--------------------------------------------------------------------------------
-- Tests for registering/deregistering mock titles
--------------------------------------------------------------------------------

local MOCK_WP_SANDBOX_ARGS = {
	title = 'Wikipedia:Sandbox',
	content = 'Some random content: zbS8mJ31l8eY',
	isRedirect = true -- Cannot be true for a real page with the above content, so we can prove it's a mock
}
local MOCK_WP_SANDBOX = mMockTitle.MockTitle(MOCK_WP_SANDBOX_ARGS)

local MOCK_MAIN_PAGE_ARGS = {
	title = 'Main Page',
	content = 'Mock main page content: 38fgbhqwu8adb',
}
local MOCK_MAIN_PAGE = mMockTitle.MockTitle(MOCK_MAIN_PAGE_ARGS)

function suite:tearDownRegistrationTests()
	mMockTitle.clearAllMockTitles()
end

suite["test registerMockTitle: real titles are used during patching when no mocks are registered"] = function (self)
	-- Setup
	local realTitle = mw.title.new('Wikipedia:Sandbox')

	-- Test
	mMockTitle.patchTitleConstructors(function ()
		self:assertTitlesAreEqual(
			mw.title.new(MOCK_WP_SANDBOX_ARGS.title),
			realTitle
		)
	end)
	
	-- Teardown
	self:tearDownRegistrationTests()
end

local registerMockTitleTestData = {
	{argType = 'option table', arg = MOCK_WP_SANDBOX_ARGS},
	{argType = 'mock title', arg = MOCK_WP_SANDBOX},
}

for _, testData in ipairs(registerMockTitleTestData) do
	suite[string.format(
		"test registerMockTitle: mocks are registered when specified using %s",
		testData.argType
	)] = function (self)
		-- Setup
		mMockTitle.registerMockTitle(testData.arg)

		-- Test
		mMockTitle.patchTitleConstructors(function ()
			self:assertTitlesAreEqual(
				mw.title.new(MOCK_WP_SANDBOX_ARGS.title),
				MOCK_WP_SANDBOX
			)
		end)
		
		-- Teardown
		self:tearDownRegistrationTests()
	end
end

function suite:setUpDeregistrationTests()
	mMockTitle.registerMockTitles(MOCK_WP_SANDBOX_ARGS,	MOCK_MAIN_PAGE_ARGS)
end

suite["test deregisterMockTitle: previously registered titles are not deregistered if a deregistration function is not called"] = function (self)
	-- Setup
	self:setUpDeregistrationTests()

	-- Test
	mMockTitle.patchTitleConstructors(function ()
		self:assertTitlesAreEqual(
			mw.title.new(MOCK_WP_SANDBOX_ARGS.title),
			MOCK_WP_SANDBOX
		)
	end)

	-- Teardown
	self:tearDownRegistrationTests()
end

local deregisterMockTitleTestData = {
	{argType = "title", arg = MOCK_WP_SANDBOX_ARGS.title},
	{argType = "args table", arg = MOCK_WP_SANDBOX_ARGS},
	{argType = "ID", arg = MOCK_WP_SANDBOX.id},
	{argType = "title object", arg = mw.title.new(MOCK_WP_SANDBOX_ARGS.title)},
	{argType = "mock title object", arg = MOCK_WP_SANDBOX},
}

for _, testData in ipairs(deregisterMockTitleTestData) do
	suite[
		string.format(
			"test deregisterMockTitle: previously registered titles are deregistered when specified by %s",
			testData.argType
		)
	] = function (self)
		-- Setup
		self:setUpDeregistrationTests()
		local realTitle = mw.title.new(MOCK_WP_SANDBOX_ARGS.title)
		mMockTitle.deregisterMockTitle(testData.arg)

		-- Test
		mMockTitle.patchTitleConstructors(function ()
			self:assertTitlesAreEqual(
				mw.title.new(MOCK_WP_SANDBOX_ARGS.title),
				realTitle
			)
		end)

		-- Teardown
		self:tearDownRegistrationTests()
	end
end

local registerMockTitlesTestData = {
	{argType = 'option table', args = {MOCK_WP_SANDBOX_ARGS, MOCK_MAIN_PAGE_ARGS}},
	{argType = 'mock title', args = {MOCK_WP_SANDBOX, MOCK_MAIN_PAGE}},
}

for _, testData in ipairs(registerMockTitlesTestData) do
	suite[string.format(
		"test registerMockTitles: mocks are registered when specified using %s",
		testData.argType
	)] = function (self)
		-- Setup
		mMockTitle.registerMockTitles(unpack(testData.args))

		-- Test
		mMockTitle.patchTitleConstructors(function ()
			self:assertTitlesAreEqual(
				mw.title.new(MOCK_WP_SANDBOX_ARGS.title),
				MOCK_WP_SANDBOX
			)
			self:assertTitlesAreEqual(
				mw.title.new(MOCK_MAIN_PAGE_ARGS.title),
				MOCK_MAIN_PAGE
			)
		end)
		
		-- Teardown
		self:tearDownRegistrationTests()
	end
end

local deregisterMockTitlesTestData = {
	{
		argType = "title",
		args = {MOCK_WP_SANDBOX_ARGS.title, MOCK_MAIN_PAGE_ARGS.title},
	},
	{argType = "args table", args = {MOCK_WP_SANDBOX_ARGS, MOCK_MAIN_PAGE_ARGS}},
	{argType = "ID", args = {MOCK_WP_SANDBOX.id, MOCK_MAIN_PAGE.id}},
	{
		argType = "title object",
		args = {
			mw.title.new(MOCK_WP_SANDBOX_ARGS.title),
			mw.title.new(MOCK_MAIN_PAGE_ARGS.title),
		},
	},
	{argType = "mock title object", args = {MOCK_WP_SANDBOX, MOCK_MAIN_PAGE}},
}

for _, testData in ipairs(deregisterMockTitlesTestData) do
	suite[
		string.format(
			"test deregisterMockTitle: previously registered titles are deregistered when specified by %s",
			testData.argType
		)
	] = function (self)
		-- Setup
		self:setUpDeregistrationTests()
		local realSandboxTitle = mw.title.new(MOCK_WP_SANDBOX_ARGS.title)
		local realMainPageTitle = mw.title.new(MOCK_MAIN_PAGE_ARGS.title)
		mMockTitle.deregisterMockTitles(unpack(testData.args))

		-- Test
		mMockTitle.patchTitleConstructors(function ()
			self:assertTitlesAreEqual(
				mw.title.new(MOCK_WP_SANDBOX_ARGS.title),
				realSandboxTitle
			)
			self:assertTitlesAreEqual(
				mw.title.new(MOCK_MAIN_PAGE_ARGS.title),
				realMainPageTitle
			)
		end)

		-- Teardown
		self:tearDownRegistrationTests()
	end
end

suite["test clearMockTitleRegistry: all mock titles are cleared"] = function (self)
	-- Setup
	self:setUpDeregistrationTests()
	local realSandboxTitle = mw.title.new(MOCK_WP_SANDBOX_ARGS.title)
	local realMainPageTitle = mw.title.new(MOCK_MAIN_PAGE_ARGS.title)
	mMockTitle.clearMockTitleRegistry()

	-- Test
	mMockTitle.patchTitleConstructors(function ()
		self:assertTitlesAreEqual(
			mw.title.new(MOCK_WP_SANDBOX_ARGS.title),
			realSandboxTitle
		)
		self:assertTitlesAreEqual(
			mw.title.new(MOCK_MAIN_PAGE_ARGS.title),
			realMainPageTitle
		)
	end)

	-- Teardown
	self:tearDownRegistrationTests()
end

suite["test registered mocks do not share content with standalone mocks when title constructors are patched"] = function (self)
	mMockTitle.registerMockTitle{title = 'Wikipedia:Sandbox', content = 'Foo'}
	mMockTitle.patchTitleConstructors(function ()
		self:assertTitlesAreNotEqual(
			mw.title.new('Wikipedia:Sandbox'),
			mMockTitle.MockTitle{title = 'Wikipedia:Sandbox', content = 'Bar'}
		)
	end)
	self:tearDownRegistrationTests()
end

return suite