diff --git a/Testcases/IP.lua b/Testcases/IP.lua new file mode 100644 index 0000000..6823333 --- /dev/null +++ b/Testcases/IP.lua @@ -0,0 +1,1401 @@ +-- Unit tests for [[Module:IP]]. Click talk page to run tests. + +-- Unit test module setup +local ScribuntoUnit = require('Module:ScribuntoUnit') +local suite = ScribuntoUnit:new() + +-- Target module setup +local IP = require('Module:IP') +local IPAddress = IP.IPAddress +local Subnet = IP.Subnet +local IPv4Collection = IP.IPv4Collection +local IPv6Collection = IP.IPv6Collection + +-- Constants +local IP_ADDRESS_CLASS = 'IPAddress' +local IP_ADDRESS_OBJECT = 'ipAddress' +local SUBNET_CLASS = 'Subnet' +local SUBNET_OBJECT = 'subnet' +local IPV4COLLECTION_CLASS = 'IPv4Collection' +local IPV4COLLECTION_OBJECT = 'ipv4Collection' +local IPV6COLLECTION_CLASS = 'IPv6Collection' +local IPV6COLLECTION_OBJECT = 'ipv6Collection' + +------------------------------------------------------------------------------- +-- Helper methods +------------------------------------------------------------------------------- + +function suite:assertError(message, plain, ...) + local success, ret = pcall(...) + self:assertFalse(success) + self:assertStringContains(message, ret, plain) +end + +function suite:assertNotError(...) + local success = pcall(...) + self:assertTrue(success) +end + +function suite:assertSelfParameterError(class, objName, method, ...) + local message = string.format( + 'IP: invalid %s object. Did you call %s with a dot instead of a colon, i.e. %s.%s() instead of %s:%s()?', + class, method, objName, method, objName, method + ) + self:assertError(message, true, ...) +end + +function suite:assertTypeError(argIdx, funcName, expected, received, ...) + local message = string.format( + "bad argument #%d to '%s' (%s expected, got %s)", + argIdx, funcName, expected, received + ) + self:assertError(message, true, ...) +end + +function suite:assertObjectError(argIdx, funcName, className, ...) + local message = string.format( + "bad argument #%d to '%s' (not a valid %s object)", + argIdx, funcName, className + ) + self:assertError(message, true, ...) +end + +function suite:assertIPStringError(ipStr, ...) + local message = string.format("'%s' is an invalid IP address", ipStr) + self:assertError(message, true, ...) +end + +function suite:assertCIDRStringError(cidr, ...) + local message = string.format("'%s' is an invalid CIDR string", cidr) + self:assertError(message, true, ...) +end + +function suite:assertMetatableProtected(obj) + self:assertError('cannot change a protected metatable', true, function () + setmetatable(obj, {}) + end) +end + +function suite:assertNotMetatable(val) + self:assertFalse(type(val) == 'table' and type(val.__eq) == 'function') +end + +function suite:assertObject(val, ...) + self:assertTrue(type(val) == 'table') + for i, method in ipairs{...} do + self:assertTrue(type(val[method]) == 'function') + end +end + +function suite:assertIPAddressObject(val) + suite:assertObject(val, 'getIP', 'isInSubnet') +end + +function suite:assertSubnetObject(val) + suite:assertObject(val, 'getCIDR', 'containsIP') +end + +function suite:assertCollectionObject(val) + suite:assertObject(val, 'addIP', 'addSubnet') +end + +function suite:assertIPv4CollectionObject(val) + self:assertCollectionObject(val) + self:assertEquals('IPv4', val:getVersion()) +end + +function suite:assertIPv6CollectionObject(val) + self:assertCollectionObject(val) + self:assertEquals('IPv6', val:getVersion()) +end + +function suite:assertRangesEqual(expected, actual) + self:assertTrue(#expected == #actual) + for i = 1, #expected do + local expectedRange = expected[i] + local actualRange = actual[i] + self:assertEquals(expectedRange[1], actualRange[1]) + self:assertEquals(expectedRange[2], actualRange[2]) + end +end + +------------------------------------------------------------------------------- +-- IPAddress tests +------------------------------------------------------------------------------- + +function suite:testIPConstructor() + local function assertValidIP(ip) + self:assertIPAddressObject(IPAddress.new(ip)) + end + assertValidIP('1.2.3.4') + assertValidIP('0.0.0.0') + assertValidIP('255.255.255.255') + assertValidIP('::') + assertValidIP('ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff') + assertValidIP('2001::f:1234') +end + +function suite:testInvalidIPs() + local function assertInvalidIP(ip) + self:assertError( + string.format("'%s' is an invalid IP address", ip), + true, + function () + IPAddress.new(ip) + end + ) + end + assertInvalidIP('') + assertInvalidIP('foo') + assertInvalidIP('01.02.03.04') + assertInvalidIP('256.256.256.256') + assertInvalidIP('1.2.3') + assertInvalidIP('1.2.3.4.5') + assertInvalidIP('-1.2.3.4') + assertInvalidIP(':') + -- TODO: work out what to do about the following test + -- assertInvalidIP(':::') + assertInvalidIP('2001::f::1234') + assertInvalidIP('2001:g::') +end + +function suite:testIPConstructorErrors() + self:assertTypeError( + 1, 'IPAddress.new', 'string', 'boolean', + function () + IPAddress.new(true) + end + ) + self:assertTypeError( + 1, 'IPAddress.new', 'string', 'number', + function () + IPAddress.new(7) + end + ) + self:assertTypeError( + 1, 'IPAddress.new', 'string', 'nil', + function () + IPAddress.new() + end + ) + self:assertError( + "'foo' is an invalid IP address", + true, + function () + IPAddress.new('foo') + end + ) +end + +function suite:testIPEquals() + self:assertTrue(IPAddress.new('1.2.3.4') == IPAddress.new('1.2.3.4')) + self:assertFalse(IPAddress.new('1.2.3.5') == IPAddress.new('1.2.3.4')) + self:assertTrue(IPAddress.new('2001:a1:b2::') == IPAddress.new('2001:a1:b2::')) + self:assertTrue(IPAddress.new('::') == IPAddress.new('0:0:0:0:0:0:0:0')) +end + +function suite:testIPLessThan() + self:assertFalse(IPAddress.new('1.2.3.4') < IPAddress.new('1.2.3.4')) + self:assertFalse(IPAddress.new('1.2.3.5') < IPAddress.new('1.2.3.4')) + self:assertTrue(IPAddress.new('1.2.3.3') < IPAddress.new('1.2.3.4')) + self:assertTrue(IPAddress.new('2.0.0.0') < IPAddress.new('10.0.0.0')) + self:assertTrue(IPAddress.new('2001:a1:b2::') < IPAddress.new('2001:a1:b2::1')) + self:assertTrue(IPAddress.new('2001:b::') < IPAddress.new('2001:10::')) +end + +function suite:testIPLessThanOrEqualTo() + self:assertTrue(IPAddress.new('1.2.3.4') <= IPAddress.new('1.2.3.4')) + self:assertFalse(IPAddress.new('1.2.3.5') <= IPAddress.new('1.2.3.4')) + self:assertTrue(IPAddress.new('1.2.3.3') <= IPAddress.new('1.2.3.4')) +end + +function suite:testIPToString() + self:assertEquals('1.2.3.4', tostring(IPAddress.new('1.2.3.4'))) + self:assertEquals('2001:a1:b2::', tostring(IPAddress.new('2001:a1:b2:0:0:0:0:0'))) +end + +function suite:testConcatIP() + self:assertEquals( + '1.2.3.45.6.7.8', + IPAddress.new('1.2.3.4') .. IPAddress.new('5.6.7.8') + ) + self:assertEquals('1.2.3.4foo', IPAddress.new('1.2.3.4') .. 'foo') +end + +function suite:testGetIP() + self:assertEquals('1.2.3.4', IPAddress.new('1.2.3.4'):getIP()) +end + +function suite:testGetIPErrors() + self:assertSelfParameterError( + IP_ADDRESS_CLASS, IP_ADDRESS_OBJECT, 'getIP', + function () + IPAddress.new('1.2.3.4').getIP() + end + ) + self:assertSelfParameterError( + IP_ADDRESS_CLASS, IP_ADDRESS_OBJECT, 'getIP', + function () + IPAddress.new('1.2.3.4').getIP(IPAddress.new('5.6.7.8')) + end + ) +end + +function suite:testGetVersion() + self:assertEquals('IPv4', IPAddress.new('1.2.3.4'):getVersion()) + self:assertEquals('IPv6', IPAddress.new('2001:db8::ff00:12:3456'):getVersion()) +end + +function suite:testGetVersionErrors() + self:assertSelfParameterError( + IP_ADDRESS_CLASS, IP_ADDRESS_OBJECT, 'getVersion', + function () + IPAddress.new('1.2.3.4').getVersion() + end + ) + self:assertSelfParameterError( + IP_ADDRESS_CLASS, IP_ADDRESS_OBJECT, 'getVersion', + function () + IPAddress.new('1.2.3.4').getVersion(IPAddress.new('5.6.7.8')) + end + ) +end + +function suite:testIsIPv4() + self:assertTrue(IPAddress.new('1.2.3.4'):isIPv4()) + self:assertFalse(IPAddress.new('2001:db8::ff00:12:3456'):isIPv4()) +end + +function suite:testIsIPv4Errors() + self:assertSelfParameterError( + IP_ADDRESS_CLASS, IP_ADDRESS_OBJECT, 'isIPv4', + function () + IPAddress.new('1.2.3.4').isIPv4() + end + ) + self:assertSelfParameterError( + IP_ADDRESS_CLASS, IP_ADDRESS_OBJECT, 'isIPv4', + function () + IPAddress.new('1.2.3.4').isIPv4(IPAddress.new('5.6.7.8')) + end + ) +end + +function suite:testIsIPv6() + self:assertTrue(IPAddress.new('2001:db8::ff00:12:3456'):isIPv6()) + self:assertFalse(IPAddress.new('1.2.3.4'):isIPv6()) +end + +function suite:testIsIPv6Errors() + self:assertSelfParameterError( + IP_ADDRESS_CLASS, IP_ADDRESS_OBJECT, 'isIPv6', + function () + IPAddress.new('1.2.3.4').isIPv6() + end + ) + self:assertSelfParameterError( + IP_ADDRESS_CLASS, IP_ADDRESS_OBJECT, 'isIPv6', + function () + IPAddress.new('1.2.3.4').isIPv6(IPAddress.new('5.6.7.8')) + end + ) +end + +function suite:testIsInSubnet() + self:assertTrue(IPAddress.new('1.2.3.4'):isInSubnet(Subnet.new('1.2.3.0/24'))) + self:assertFalse(IPAddress.new('1.2.3.4'):isInSubnet(Subnet.new('1.2.4.0/24'))) +end + +function suite:testIsInSubnetFromString() + self:assertTrue(IPAddress.new('1.2.3.4'):isInSubnet('1.2.3.0/24')) + self:assertFalse(IPAddress.new('1.2.3.4'):isInSubnet('1.2.4.0/24')) +end + +function suite:testIsInSubnetErrors() + self:assertSelfParameterError( + IP_ADDRESS_CLASS, IP_ADDRESS_OBJECT, 'isInSubnet', + function () + IPAddress.new('1.2.3.4').isInSubnet() + end + ) + self:assertSelfParameterError( + IP_ADDRESS_CLASS, IP_ADDRESS_OBJECT, 'isInSubnet', + function () + IPAddress.new('1.2.3.4').isInSubnet(IPAddress.new('5.6.7.8')) + end + ) + self:assertTypeError( + 1, 'isInSubnet', 'string or table', 'boolean', + function () + IPAddress.new('1.2.3.4'):isInSubnet(false) + end + ) + self:assertTypeError( + 1, 'isInSubnet', 'string or table', 'nil', + function () + IPAddress.new('1.2.3.4'):isInSubnet() + end + ) + self:assertCIDRStringError( + 'foo', + function () + IPAddress.new('1.2.3.4'):isInSubnet('foo') + end + ) + self:assertObjectError( + 1, 'isInSubnet', 'Subnet', + function () + IPAddress.new('1.2.3.4'):isInSubnet{foo = 'bar'} + end + ) +end + +function suite:testGetSubnet() + self:assertEquals( + '1.2.3.0/24', + IPAddress.new('1.2.3.4'):getSubnet(24):getCIDR() + ) + self:assertEquals( + '1.2.3.128/25', + IPAddress.new('1.2.3.130'):getSubnet(25):getCIDR() + ) +end + +function suite:testGetSubnetErrors() + self:assertSelfParameterError( + IP_ADDRESS_CLASS, IP_ADDRESS_OBJECT, 'getSubnet', + function () + IPAddress.new('1.2.3.4').getSubnet(24) + end + ) + self:assertSelfParameterError( + IP_ADDRESS_CLASS, IP_ADDRESS_OBJECT, 'getSubnet', + function () + IPAddress.new('1.2.3.4').getSubnet(IPAddress.new('5.6.7.8'), 24) + end + ) + self:assertTypeError( + 1, 'getSubnet', 'number', 'boolean', + function () + IPAddress.new('1.2.3.4'):getSubnet(false) + end + ) + self:assertTypeError( + 1, 'getSubnet', 'number', 'nil', + function () + IPAddress.new('1.2.3.4'):getSubnet() + end + ) +end + +function suite:testGetSubnetIPv4NumberErrors() + local message = "bad argument #1 to 'getSubnet' (must be an integer between 0 and 32)" + self:assertError(message, true, function () + IPAddress.new('1.2.3.4'):getSubnet(33) + end) + self:assertError(message, true, function () + IPAddress.new('1.2.3.4'):getSubnet(-1) + end) + self:assertError(message, true, function () + IPAddress.new('1.2.3.4'):getSubnet(24.5) + end) +end + +function suite:testGetSubnetIPv6NumberErrors() + local message = "bad argument #1 to 'getSubnet' (must be an integer between 0 and 128)" + self:assertError(message, true, function () + IPAddress.new('2001:db8::ff00:12:3456'):getSubnet(129) + end) + self:assertError(message, true, function () + IPAddress.new('2001:db8::ff00:12:3456'):getSubnet(-1) + end) + self:assertError(message, true, function () + IPAddress.new('2001:db8::ff00:12:3456'):getSubnet(112.5) + end) +end + +function suite:testGetNextIP() + self:assertEquals('1.2.3.5', tostring(IPAddress.new('1.2.3.4'):getNextIP())) + self:assertEquals( + IPAddress.new('2001:db8::ff00:12:3457'), + IPAddress.new('2001:db8::ff00:12:3456'):getNextIP() + ) +end + +function suite:testGetNextIPWraparound() + self:assertEquals( + IPAddress.new('0.0.0.0'), + IPAddress.new('255.255.255.255'):getNextIP() + ) + self:assertEquals( + '::', + tostring(IPAddress.new('ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff'):getNextIP()) + ) +end + +function suite:testGetNextIPErrors() + self:assertSelfParameterError( + IP_ADDRESS_CLASS, IP_ADDRESS_OBJECT, 'getNextIP', + function () + IPAddress.new('1.2.3.4').getNextIP() + end + ) + self:assertSelfParameterError( + IP_ADDRESS_CLASS, IP_ADDRESS_OBJECT, 'getNextIP', + function () + IPAddress.new('1.2.3.4').getNextIP(IPAddress.new('5.6.7.8')) + end + ) +end + +function suite:testGetPreviousIP() + self:assertEquals( + IPAddress.new('1.2.3.3'), + IPAddress.new('1.2.3.4'):getPreviousIP() + ) + self:assertEquals( + IPAddress.new('2001:db8::ff00:12:3455'), + IPAddress.new('2001:db8::ff00:12:3456'):getPreviousIP() + ) +end + +function suite:testGetPreviousIPWraparound() + self:assertEquals( + IPAddress.new('255.255.255.255'), + IPAddress.new('0.0.0.0'):getPreviousIP() + ) + self:assertEquals( + IPAddress.new('ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff'), + IPAddress.new('::'):getPreviousIP() + ) +end + +function suite:testGetPreviousIPErrors() + self:assertSelfParameterError( + IP_ADDRESS_CLASS, IP_ADDRESS_OBJECT, 'getPreviousIP', + function () + IPAddress.new('1.2.3.4').getPreviousIP() + end + ) + self:assertSelfParameterError( + IP_ADDRESS_CLASS, IP_ADDRESS_OBJECT, 'getPreviousIP', + function () + IPAddress.new('1.2.3.4').getPreviousIP(IPAddress.new('5.6.7.8')) + end + ) +end + +function suite:testGetIPMetatable() + self:assertNotMetatable(getmetatable(IPAddress.new('1.2.3.4'))) +end + +function suite:testSetIPMetatable() + self:assertMetatableProtected(IPAddress.new('1.2.3.4')) +end + +------------------------------------------------------------------------------- +-- Subnet tests +------------------------------------------------------------------------------- + +function suite:testValidCIDRs() + local function assertValidCIDR(cidr) + self:assertTrue(type(Subnet.new(cidr)) == 'table') + end + assertValidCIDR('1.2.3.0/24') + assertValidCIDR(' 1.2.3.0/24 ') + assertValidCIDR('0.0.0.0/0') + assertValidCIDR('0.0.0.0/32') + assertValidCIDR('255.255.255.255/32') + assertValidCIDR('2001:db8::ff00:12:0/122') + assertValidCIDR('2001:DB8::FF00:12:0/122') + assertValidCIDR('::/0') + assertValidCIDR('::/128') +end + +function suite:testInvalidCIDRs() + local function assertInvalidCIDR(cidr) + self:assertError( + string.format("'%s' is an invalid CIDR string", cidr), + true, + function () + Subnet.new(cidr) + end + ) + end + assertInvalidCIDR('foo') + assertInvalidCIDR('0/0') + assertInvalidCIDR('/24') + assertInvalidCIDR('1.2.3/24') + assertInvalidCIDR(':/0') + assertInvalidCIDR('1.2.3.4') + assertInvalidCIDR('0.0.0.0/33') + assertInvalidCIDR('0.0.0.0/-1') + assertInvalidCIDR('256.0.0.0/24') + assertInvalidCIDR('1.2.3.4/24') + assertInvalidCIDR('1.2.3.0/16') + assertInvalidCIDR('0.0.0.0/01') -- Leading zero in bit length + assertInvalidCIDR('2001:db8::ff00:12:3456') + assertInvalidCIDR('2001:db8::ff00:12:0/foo') + assertInvalidCIDR('::/-1') + assertInvalidCIDR('::/129') + assertInvalidCIDR('gggg:db8::ff00:12:0/122') + assertInvalidCIDR('2001:db8::ff00:12:3456/122') + assertInvalidCIDR('2001:db8::ff00:12:0/106') +end + +function suite:testSubnetConstructorErrors() + self:assertTypeError( + 1, 'Subnet.new', 'string', 'boolean', + function () + Subnet.new(true) + end + ) + self:assertTypeError( + 1, 'Subnet.new', 'string', 'number', + function () + Subnet.new(7) + end + ) + self:assertTypeError( + 1, 'Subnet.new', 'string', 'nil', + function () + Subnet.new() + end + ) +end + +function suite:testSubnetEquals() + self:assertTrue(Subnet.new('1.2.3.0/24') == Subnet.new('1.2.3.0/24')) + self:assertFalse(Subnet.new('1.2.3.0/24') == Subnet.new('1.2.0.0/16')) +end + +function suite:testConcatSubnet() + self:assertEquals( + '1.2.3.0/244.5.6.0/24', + Subnet.new('1.2.3.0/24') .. Subnet.new('4.5.6.0/24') + ) + self:assertEquals('1.2.3.0/24foo', Subnet.new('1.2.3.0/24') .. 'foo') + self:assertEquals('foo1.2.3.0/24', 'foo' .. Subnet.new('1.2.3.0/24')) +end + +function suite:testSubnetToString() + self:assertEquals('1.2.3.0/24', tostring(Subnet.new('1.2.3.0/24'))) + self:assertEquals( + '2001:db8::ff00:12:0/122', + tostring(Subnet.new('2001:db8::ff00:12:0/122')) + ) +end + +function suite:testSubnetGetmetatable() + self:assertNotMetatable(getmetatable(Subnet.new('1.2.3.0/24'))) +end + +function suite:testSubnetSetmetatable() + self:assertMetatableProtected(Subnet.new('1.2.3.0/24')) +end + +function suite:testSubnetGetPrefix() + self:assertEquals( + IPAddress.new('1.2.3.0'), + Subnet.new('1.2.3.0/24'):getPrefix() + ) +end + +function suite:testSubnetGetPrefixErrors() + self:assertSelfParameterError( + SUBNET_CLASS, SUBNET_OBJECT, 'getPrefix', + function () + Subnet.new('1.2.3.0/24').getPrefix() + end + ) + self:assertSelfParameterError( + SUBNET_CLASS, SUBNET_OBJECT, 'getPrefix', + function () + Subnet.new('1.2.3.0/24').getPrefix(Subnet.new('4.5.6.0/24')) + end + ) +end + +function suite:testSubnetGetHighestIP() + self:assertEquals( + IPAddress.new('1.2.3.255'), + Subnet.new('1.2.3.0/24'):getHighestIP() + ) +end + +function suite:testGetHighestIPFromGetSubnet() + self:assertEquals( + IPAddress.new('1.2.3.255'), + IPAddress.new('1.2.3.4'):getSubnet(24):getHighestIP() + ) +end + +function suite:testSubnetGetHighestIPErrors() + self:assertSelfParameterError( + SUBNET_CLASS, SUBNET_OBJECT, 'getHighestIP', + function () + Subnet.new('1.2.3.0/24').getHighestIP() + end + ) + self:assertSelfParameterError( + SUBNET_CLASS, SUBNET_OBJECT, 'getHighestIP', + function () + Subnet.new('1.2.3.0/24').getHighestIP(Subnet.new('4.5.6.0/24')) + end + ) +end + +function suite:testSubnetGetBitLength() + self:assertEquals(24, Subnet.new('1.2.3.0/24'):getBitLength()) +end + +function suite:testSubnetGetBitLengthErrors() + self:assertSelfParameterError( + SUBNET_CLASS, SUBNET_OBJECT, 'getBitLength', + function () + Subnet.new('1.2.3.0/24').getBitLength() + end + ) +end + +function suite:testSubnetGetCIDR() + self:assertEquals('1.2.3.0/24', Subnet.new('1.2.3.0/24'):getCIDR()) +end + +function suite:testGetCIDRErrors() + self:assertSelfParameterError( + SUBNET_CLASS, SUBNET_OBJECT, 'getCIDR', + function () + Subnet.new('1.2.3.0/24').getCIDR() + end + ) + self:assertSelfParameterError( + SUBNET_CLASS, SUBNET_OBJECT, 'getCIDR', + function () + Subnet.new('1.2.3.0/24').getCIDR(Subnet.new('4.5.6.0/24')) + end + ) +end + +function suite:testSubnetGetVersion() + self:assertEquals('IPv4', Subnet.new('1.2.3.0/24'):getVersion()) + self:assertEquals('IPv6', Subnet.new('2001:db8::ff00:0:0/96'):getVersion()) +end + +function suite:testSubnetGetVersionErrors() + self:assertSelfParameterError( + SUBNET_CLASS, SUBNET_OBJECT, 'getVersion', + function () + Subnet.new('1.2.3.0/24').getVersion() + end + ) + self:assertSelfParameterError( + SUBNET_CLASS, SUBNET_OBJECT, 'getVersion', + function () + Subnet.new('1.2.3.0/24').getVersion(Subnet.new('4.5.6.0/24')) + end + ) +end + +function suite:testSubnetIsIPv4() + self:assertTrue(Subnet.new('1.2.3.0/24'):isIPv4()) + self:assertFalse(Subnet.new('2001:db8::ff00:0:0/96'):isIPv4()) +end + +function suite:testSubnetIsIPv4Errors() + self:assertSelfParameterError( + SUBNET_CLASS, SUBNET_OBJECT, 'isIPv4', + function () + Subnet.new('1.2.3.0/24').isIPv4() + end + ) + self:assertSelfParameterError( + SUBNET_CLASS, SUBNET_OBJECT, 'isIPv4', + function () + Subnet.new('1.2.3.0/24').isIPv4(Subnet.new('4.5.6.0/24')) + end + ) +end + +function suite:testSubnetIsIPv6() + self:assertTrue(Subnet.new('2001:db8::ff00:0:0/96'):isIPv6()) + self:assertFalse(Subnet.new('1.2.3.0/24'):isIPv6()) +end + +function suite:testSubnetIsIPv6Errors() + self:assertSelfParameterError( + SUBNET_CLASS, SUBNET_OBJECT, 'isIPv6', + function () + Subnet.new('1.2.3.0/24').isIPv6() + end + ) + self:assertSelfParameterError( + SUBNET_CLASS, SUBNET_OBJECT, 'isIPv6', + function () + Subnet.new('1.2.3.0/24').isIPv6(Subnet.new('4.5.6.0/24')) + end + ) +end + +function suite:testSubnetContainsIP() + self:assertTrue( + Subnet.new('1.2.3.0/24'):containsIP(IPAddress.new('1.2.3.4')) + ) + self:assertFalse( + Subnet.new('1.2.3.0/24'):containsIP(IPAddress.new('1.2.4.4')) + ) +end + +function suite:testSubnetContainsIPErrors() + self:assertSelfParameterError( + SUBNET_CLASS, SUBNET_OBJECT, 'containsIP', + function () + Subnet.new('1.2.3.0/24').containsIP(IPAddress.new('1.2.3.4')) + end + ) + self:assertSelfParameterError( + SUBNET_CLASS, SUBNET_OBJECT, 'containsIP', + function () + Subnet.new('1.2.3.0/24').containsIP( + Subnet.new('4.5.6.0/24'), + IPAddress.new('1.2.3.4') + ) + end + ) + self:assertTypeError( + 1, 'containsIP', 'string or table', 'boolean', + function () + Subnet.new('1.2.3.0/24'):containsIP(false) + end + ) + self:assertTypeError( + 1, 'containsIP', 'string or table', 'nil', + function () + Subnet.new('1.2.3.0/24'):containsIP() + end + ) + self:assertIPStringError( + 'foo', + function () + Subnet.new('1.2.3.0/24'):containsIP('foo') + end + ) + self:assertObjectError( + 1, 'containsIP', 'IPAddress', + function () + Subnet.new('1.2.3.0/24'):containsIP{foo = 'bar'} + end + ) +end + +function suite:testOverlapsSubnet() + self:assertTrue( + Subnet.new('1.2.0.0/16'):overlapsSubnet(Subnet.new('1.2.3.0/24')) + ) + self:assertFalse( + Subnet.new('1.2.0.0/16'):overlapsSubnet(Subnet.new('1.3.3.0/24')) + ) +end + +function suite:testOverlapsSubnetErrors() + self:assertSelfParameterError( + SUBNET_CLASS, SUBNET_OBJECT, 'overlapsSubnet', + function () + Subnet.new('1.2.3.0/24').overlapsSubnet(Subnet.new('1.2.0.0/16')) + end + ) + self:assertSelfParameterError( + SUBNET_CLASS, SUBNET_OBJECT, 'overlapsSubnet', + function () + Subnet.new('1.2.3.0/24').overlapsSubnet( + Subnet.new('4.5.6.0/24'), + Subnet.new('1.2.0.0/16') + ) + end + ) + self:assertTypeError( + 1, 'overlapsSubnet', 'string or table', 'boolean', + function () + Subnet.new('1.2.3.0/24'):overlapsSubnet(false) + end + ) + self:assertTypeError( + 1, 'overlapsSubnet', 'string or table', 'nil', + function () + Subnet.new('1.2.3.0/24'):overlapsSubnet() + end + ) + self:assertCIDRStringError( + 'foo', + function () + Subnet.new('1.2.3.0/24'):overlapsSubnet('foo') + end + ) + self:assertObjectError( + 1, 'overlapsSubnet', 'Subnet', + function () + Subnet.new('1.2.3.0/24'):overlapsSubnet{foo = 'bar'} + end + ) +end + +function suite:testWalkSubnet() + do + local ips = {} + for ip in Subnet.new('1.2.3.0/30'):walk() do + ips[#ips + 1] = tostring(ip) + end + self:assertEquals( + '1.2.3.0 1.2.3.1 1.2.3.2 1.2.3.3', + table.concat(ips, ' ') + ) + end + do + local ips = {} + for ip in Subnet.new('2001:db8::ff00:0:0/126'):walk() do + ips[#ips + 1] = tostring(ip) + end + self:assertEquals( + '2001:db8::ff00:0:0 2001:db8::ff00:0:1 2001:db8::ff00:0:2 2001:db8::ff00:0:3', + table.concat(ips, ' ') + ) + end +end + +function suite:testWalkSubnetErrors() + self:assertSelfParameterError( + SUBNET_CLASS, SUBNET_OBJECT, 'walk', + function () + Subnet.new('1.2.3.0/24').walk() + end + ) + self:assertSelfParameterError( + SUBNET_CLASS, SUBNET_OBJECT, 'walk', + function () + Subnet.new('1.2.3.0/24').walk(Subnet.new('4.5.6.0/24')) + end + ) +end + +------------------------------------------------------------------------------- +-- IPv4Collection tests +------------------------------------------------------------------------------- + +function suite:testIPv4CollectionConstructor() + self:assertIPv4CollectionObject(IPv4Collection.new()) +end + +function suite:testIPv4CollectionGetVersion() + self:assertEquals('IPv4', IPv4Collection.new():getVersion()) +end + +function suite:testIPv4CollectionAddIP() + self:assertNotError(function () IPv4Collection.new():addIP('1.2.3.4') end) + self:assertNotError(function () IPv4Collection.new():addIP(IPAddress.new('1.2.3.4')) end) + suite:assertIPStringError( + 'foo', + function () + IPv4Collection.new():addIP(IPAddress.new('foo')) + end + ) + suite:assertIPStringError( + '1.2.3.0/24', + function () + IPv4Collection.new():addIP(IPAddress.new('1.2.3.0/24')) + end + ) +end + +function suite:testIPv4CollectionAddIPChaining() + self:assertNotError(function () + IPv4Collection.new() + :addIP('1.2.3.4') + :addIP('5.6.7.8') + end) +end + +function suite:testIPv4CollectionAddIPErrors() + self:assertTypeError( + 1, 'addIP', 'string or table', 'boolean', + function () + IPv4Collection.new():addIP(false) + end + ) + self:assertTypeError( + 1, 'addIP', 'string or table', 'nil', + function () + IPv4Collection.new():addIP() + end + ) + self:assertIPStringError( + 'foo', + function () + IPv4Collection.new():addIP('foo') + end + ) + self:assertObjectError( + 1, 'addIP', 'IPAddress', + function () + IPv4Collection.new():addIP{foo = 'bar'} + end + ) +end + +function suite:testIPv4CollectionAddSubnet() + self:assertNotError(function () IPv4Collection.new():addSubnet('1.2.3.0/24') end) + self:assertNotError(function () IPv4Collection.new():addSubnet(Subnet.new('1.2.3.0/24')) end) + suite:assertCIDRStringError( + 'foo', + function () + IPv4Collection.new():addSubnet('foo') + end + ) + suite:assertCIDRStringError( + '1.2.3.4', + function () + IPv4Collection.new():addSubnet('1.2.3.4') + end + ) +end + +function suite:testIPv4CollectionAddSubnetChaining() + self:assertNotError(function () + IPv4Collection.new() + :addSubnet('1.2.3.0/24') + :addSubnet('5.6.7.0/24') + end) +end + +function suite:testIPv4CollectionAddSubnetErrors() + self:assertTypeError( + 1, 'addSubnet', 'string or table', 'boolean', + function () + IPv4Collection.new():addSubnet(false) + end + ) + self:assertTypeError( + 1, 'addSubnet', 'string or table', 'nil', + function () + IPv4Collection.new():addSubnet() + end + ) + self:assertCIDRStringError( + 'foo', + function () + IPv4Collection.new():addSubnet('foo') + end + ) + self:assertObjectError( + 1, 'addSubnet', 'Subnet', + function () + IPv4Collection.new():addSubnet{foo = 'bar'} + end + ) +end + +function suite:testIPv4CollectionContainsIP() + local collection = IPv4Collection.new() + collection:addIP('1.2.3.4') + self:assertEquals(true, collection:containsIP('1.2.3.4')) + self:assertEquals(true, collection:containsIP(IPAddress.new('1.2.3.4'))) + self:assertEquals(false, collection:containsIP('1.2.3.5')) +end + +function suite:testIPv4CollectionContainsIPErrors() + self:assertTypeError( + 1, 'containsIP', 'string or table', 'boolean', + function () + IPv4Collection.new():containsIP(false) + end + ) + self:assertTypeError( + 1, 'containsIP', 'string or table', 'nil', + function () + IPv4Collection.new():containsIP() + end + ) + self:assertIPStringError( + 'foo', + function () + IPv4Collection.new():containsIP('foo') + end + ) + self:assertObjectError( + 1, 'containsIP', 'IPAddress', + function () + IPv4Collection.new():containsIP{foo = 'bar'} + end + ) +end + +function suite:testIPv4CollectionGetRanges() + local collection = IPv4Collection.new() + collection:addSubnet('1.2.0.0/24') + collection:addSubnet('1.2.1.0/24') + self:assertRangesEqual( + {{IPAddress.new('1.2.0.0'), IPAddress.new('1.2.1.255')}}, + collection:getRanges() + ) + collection:addSubnet('1.2.10.0/24') + self:assertRangesEqual( + { + {IPAddress.new('1.2.0.0'), IPAddress.new('1.2.1.255')}, + {IPAddress.new('1.2.10.0'), IPAddress.new('1.2.10.255')}, + }, + collection:getRanges() + ) +end + +function suite:testIPv4CollectionOverlapsSubnet() + local collection = IPv4Collection.new() + self:assertEquals(false, collection:overlapsSubnet('1.2.3.0/24')) + collection:addIP('1.2.3.4') + self:assertEquals(true, collection:overlapsSubnet('1.2.3.0/24')) + self:assertEquals(false, collection:overlapsSubnet('5.6.7.0/24')) +end + +function suite:testIPv4CollectionOverlapsSubnetObjects() + local collection = IPv4Collection.new() + self:assertEquals(false, collection:overlapsSubnet(Subnet.new('1.2.3.0/24'))) + collection:addIP('1.2.3.4') + self:assertEquals(true, collection:overlapsSubnet(Subnet.new('1.2.3.0/24'))) + self:assertEquals(false, collection:overlapsSubnet(Subnet.new('5.6.7.0/24'))) +end + +function suite:testIPv4CollectionOverlapsSubnetErrors() + self:assertTypeError( + 1, 'overlapsSubnet', 'string or table', 'boolean', + function () + IPv4Collection.new():overlapsSubnet(false) + end + ) + self:assertTypeError( + 1, 'overlapsSubnet', 'string or table', 'nil', + function () + IPv4Collection.new():overlapsSubnet() + end + ) + self:assertCIDRStringError( + 'foo', + function () + IPv4Collection.new():overlapsSubnet('foo') + end + ) + self:assertObjectError( + 1, 'overlapsSubnet', 'Subnet', + function () + IPv4Collection.new():overlapsSubnet{foo = 'bar'} + end + ) +end + +function suite:testIPv4CollectionAddFromString() + local collection = IPv4Collection.new() + collection:addFromString('foo 1.2.3.4 bar 5.6.7.0/24 baz') + self:assertTrue(collection:containsIP('1.2.3.4')) + self:assertTrue(collection:overlapsSubnet('5.6.7.0/24')) +end + +function suite:testIPv4CollectionAddFromStringChaining() + self:assertNotError(function () + IPv4Collection.new() + :addFromString('foo 1.2.3.4') + :addFromString('bar 5.6.7.8') + end) +end + +function suite:testIPv4CollectionAddFromStringErrors() + self:assertTypeError( + 1, 'addFromString', 'string', 'boolean', + function () + IPv4Collection.new():addFromString(false) + end + ) + self:assertTypeError( + 1, 'addFromString', 'string', 'nil', + function () + IPv4Collection.new():addFromString() + end + ) + self:assertTypeError( + 1, 'addFromString', 'string', 'table', + function () + IPv4Collection.new():addFromString{} + end + ) + self:assertTypeError( + 1, 'addFromString', 'string', 'number', + function () + IPv4Collection.new():addFromString(7) + end + ) +end + +------------------------------------------------------------------------------- +-- IPv6Collection tests +------------------------------------------------------------------------------- + +function suite:testIPv6CollectionConstructor() + self:assertIPv6CollectionObject(IPv6Collection.new()) +end + +function suite:testIPv6CollectionGetVersion() + self:assertEquals('IPv6', IPv6Collection.new():getVersion()) +end + +function suite:testIPv6CollectionAddIP() + self:assertNotError(function () IPv6Collection.new():addIP('2001:db8::ff00:12:3456') end) + self:assertNotError(function () IPv6Collection.new():addIP(IPAddress.new('2001:db8::ff00:12:3456')) end) + suite:assertIPStringError( + 'foo', + function () + IPv6Collection.new():addIP(IPAddress.new('foo')) + end + ) + suite:assertIPStringError( + '2001:db8::ff00:12:0/112', + function () + IPv6Collection.new():addIP(IPAddress.new('2001:db8::ff00:12:0/112')) + end + ) +end + +function suite:testIPv6CollectionAddIPChaining() + self:assertNotError(function () + IPv6Collection.new() + :addIP('2001:db8::ff00:0:1234') + :addIP('2001:db8::ff00:0:5678') + end) +end + +function suite:testIPv6CollectionAddIPErrors() + self:assertTypeError( + 1, 'addIP', 'string or table', 'boolean', + function () + IPv6Collection.new():addIP(false) + end + ) + self:assertTypeError( + 1, 'addIP', 'string or table', 'nil', + function () + IPv6Collection.new():addIP() + end + ) + self:assertIPStringError( + 'foo', + function () + IPv6Collection.new():addIP('foo') + end + ) + self:assertObjectError( + 1, 'addIP', 'IPAddress', + function () + IPv6Collection.new():addIP{foo = 'bar'} + end + ) +end + +function suite:testIPv6CollectionAddSubnet() + self:assertNotError(function () IPv6Collection.new():addSubnet('2001:db8::ff00:12:0/112') end) + self:assertNotError(function () IPv6Collection.new():addSubnet(Subnet.new('2001:db8::ff00:12:0/112')) end) + suite:assertCIDRStringError( + 'foo', + function () + IPv6Collection.new():addSubnet('foo') + end + ) + suite:assertCIDRStringError( + '2001:db8::ff00:12:3456', + function () + IPv6Collection.new():addSubnet('2001:db8::ff00:12:3456') + end + ) +end + +function suite:testIPv6CollectionAddSubnetChaining() + self:assertNotError(function () + IPv6Collection.new() + :addSubnet('2001:db8::ff00:0:0/112') + :addSubnet('2001:db8::ff00:1:0/112') + end) +end + +function suite:testIPv6CollectionAddSubnetErrors() + self:assertTypeError( + 1, 'addSubnet', 'string or table', 'boolean', + function () + IPv6Collection.new():addSubnet(false) + end + ) + self:assertTypeError( + 1, 'addSubnet', 'string or table', 'nil', + function () + IPv6Collection.new():addSubnet() + end + ) + self:assertCIDRStringError( + 'foo', + function () + IPv6Collection.new():addSubnet('foo') + end + ) + self:assertObjectError( + 1, 'addSubnet', 'Subnet', + function () + IPv6Collection.new():addSubnet{foo = 'bar'} + end + ) +end + +function suite:testIPv6CollectionContainsIP() + local collection = IPv6Collection.new() + collection:addIP('2001:db8::ff00:12:3456') + self:assertEquals(true, collection:containsIP('2001:db8::ff00:12:3456')) + self:assertEquals(true, collection:containsIP(IPAddress.new('2001:db8::ff00:12:3456'))) + self:assertEquals(false, collection:containsIP('1.2.3.5')) +end + +function suite:testIPv6CollectionContainsIPErrors() + self:assertTypeError( + 1, 'containsIP', 'string or table', 'boolean', + function () + IPv6Collection.new():containsIP(false) + end + ) + self:assertTypeError( + 1, 'containsIP', 'string or table', 'nil', + function () + IPv6Collection.new():containsIP() + end + ) + self:assertIPStringError( + 'foo', + function () + IPv6Collection.new():containsIP('foo') + end + ) + self:assertObjectError( + 1, 'containsIP', 'IPAddress', + function () + IPv6Collection.new():containsIP{foo = 'bar'} + end + ) +end + +function suite:testIPv6CollectionGetRanges() + local collection = IPv6Collection.new() + collection:addSubnet('2001:db8::ff00:0:0/112') + collection:addSubnet('2001:db8::ff00:1:0/112') + self:assertRangesEqual( + {{IPAddress.new('2001:db8::ff00:0:0'), IPAddress.new('2001:db8::ff00:1:ffff')}}, + collection:getRanges() + ) + collection:addSubnet('2001:db8::ff00:10:0/112') + self:assertRangesEqual( + { + {IPAddress.new('2001:db8::ff00:0:0'), IPAddress.new('2001:db8::ff00:1:ffff')}, + {IPAddress.new('2001:db8::ff00:10:0'), IPAddress.new('2001:db8::ff00:10:ffff')}, + }, + collection:getRanges() + ) +end + +function suite:testIPv6CollectionOverlapsSubnet() + local collection = IPv6Collection.new() + self:assertEquals(false, collection:overlapsSubnet('2001:db8::ff00:12:0/112')) + collection:addIP('2001:db8::ff00:12:3456') + self:assertEquals(true, collection:overlapsSubnet('2001:db8::ff00:12:0/112')) + self:assertEquals(false, collection:overlapsSubnet('2001:db8::ff00:34:0/112')) +end + +function suite:testIPv6CollectionOverlapsSubnetObjects() + local collection = IPv6Collection.new() + self:assertEquals(false, collection:overlapsSubnet(Subnet.new('2001:db8::ff00:12:0/112'))) + collection:addIP('2001:db8::ff00:12:3456') + self:assertEquals(true, collection:overlapsSubnet(Subnet.new('2001:db8::ff00:12:0/112'))) + self:assertEquals(false, collection:overlapsSubnet(Subnet.new('2001:db8::ff00:34:0/112'))) +end + +function suite:testIPv6CollectionOverlapsSubnetErrors() + self:assertTypeError( + 1, 'overlapsSubnet', 'string or table', 'boolean', + function () + IPv6Collection.new():overlapsSubnet(false) + end + ) + self:assertTypeError( + 1, 'overlapsSubnet', 'string or table', 'nil', + function () + IPv6Collection.new():overlapsSubnet() + end + ) + self:assertCIDRStringError( + 'foo', + function () + IPv6Collection.new():overlapsSubnet('foo') + end + ) + self:assertObjectError( + 1, 'overlapsSubnet', 'Subnet', + function () + IPv6Collection.new():overlapsSubnet{foo = 'bar'} + end + ) +end + +function suite:testIPv6CollectionAddFromString() + local collection = IPv6Collection.new() + collection:addFromString('foo 2001:db8::ff00:12:3456 bar 2001:db8::ff00:34:0/112 baz') + self:assertTrue(collection:containsIP('2001:db8::ff00:12:3456')) + self:assertTrue(collection:overlapsSubnet('2001:db8::ff00:34:0/112')) +end + +function suite:testIPv6CollectionAddFromStringStartingColon() + local collection = IPv6Collection.new() + collection:addFromString('::12:1234 foo') + self:assertTrue(collection:containsIP('::12:1234')) +end + +function suite:testIPv6CollectionAddFromStringStartingIndent() + local collection = IPv6Collection.new() + collection:addFromString('::As I was saying, 2001:db8::ff00:12:3456 should be blocked. ~~~~') + self:assertTrue(collection:containsIP('2001:db8::ff00:12:3456')) + self:assertFalse(collection:containsIP('::')) +end + +function suite:testIPv6CollectionAddFromStringChaining() + self:assertNotError(function () + IPv6Collection.new() + :addFromString('foo 2001:db8::ff00:0:1234') + :addFromString('bar 2001:db8::ff00:0:5678') + end) +end + +function suite:testIPv6CollectionAddFromStringErrors() + self:assertTypeError( + 1, 'addFromString', 'string', 'boolean', + function () + IPv6Collection.new():addFromString(false) + end + ) + self:assertTypeError( + 1, 'addFromString', 'string', 'nil', + function () + IPv6Collection.new():addFromString() + end + ) + self:assertTypeError( + 1, 'addFromString', 'string', 'table', + function () + IPv6Collection.new():addFromString{} + end + ) + self:assertTypeError( + 1, 'addFromString', 'string', 'number', + function () + IPv6Collection.new():addFromString(7) + end + ) +end + +return suite diff --git a/Testcases/IPAddress.lua b/Testcases/IPAddress.lua new file mode 100644 index 0000000..98c6a82 --- /dev/null +++ b/Testcases/IPAddress.lua @@ -0,0 +1,130 @@ +-- Unit tests for [[Module:IPAddress]]. Click talk page to run tests. +local p = require('Module:UnitTests') + +function p:test_isIp() + self:preprocess_equals_many('{{#invoke:IPAddress/sandbox|isIp|', '}}', { + {'200.200.200.200', '4'}, + {' 200.200.200.200', ''}, -- whitespace not currently allowed + {'200.200.200.200 ', ''}, -- whitespace not currently allowed + {'200.200.256.200', ''}, + {'200.200.200.200.', ''}, + {'200.200.200', ''}, + {'200.200.200.2d0', ''}, + {'0.0.0.0', '4'}, + {'00.00.00.00', ''}, -- according to talkpage, leading zeroes unacceptable. + {'100.100.020.100', ''}, -- according to talkpage, leading zeroes unacceptable. + {'255.255.255.255', '4'}, + {'-1.0.0.0', ''}, + {'200000000000000000000000000000000000000000000000000000000000000000000000000000.200.200.200', ''}, + {'00000000000005.10.10.10', ''}, + {'00AB:0002:3008:8CFD:00AB:0002:3008:8CFD', '6'}, -- full length + {'00ab:0002:3008:8cfd:00ab:0002:3008:8cfd', '6'}, -- lowercase + {'00aB:0002:3008:8cFd:00Ab:0002:3008:8cfD', '6'}, -- mixed case + {'00AB:00002:3008:8CFD:00AB:0002:3008:8CFD', ''}, -- at most 4 digits per segment + {':0002:3008:8CFD:00AB:0002:3008:8CFD', ''}, -- can't remove all 0s from first segment unless using :: + {'00AB:0002:3008:8CFD:00AB:0002:3008:', ''}, -- can't remove all 0s from last segment unless using :: + {'AB:02:3008:8CFD:AB:02:3008:8CFD', '6'}, -- abbreviated + {'AB:02:3008:8CFD:AB:02:3008:8CFD:02', ''}, -- too long + {'AB:02:3008:8CFD::02:3008:8CFD', '6'}, -- correct use of :: + {'AB:02:3008:8CFD::02:3008:8CFD:02', ''}, -- too long + {'AB:02:3008:8CFD::02::8CFD', ''}, -- can't have two ::s + {'GB:02:3008:8CFD:AB:02:3008:8CFD', ''}, -- Invalid character G + {'::', '6'}, -- unassigned IPv6 address + {'::1', '6'}, -- loopback IPv6 address + {'0::', '6'}, -- another name for unassigned IPv6 address + {'0::0', '6'}, -- another name for unassigned IPv6 address + {'2:::3', ''}, -- illegal: three colons + }) +end + +function p:test_isIpV4Range() + self:preprocess_equals_many('{{#invoke:IPAddress/sandbox|isIpV4Range|', '}}', { + {'200.200.200.200', '0'}, + {'200.200.200.0/28', '1'}, + {'0.0.0.0', '0'}, + {'0.0.0.0/28', '1'}, + {'00AB:0002:3008:8CFD:00AB:0002:3008:8CFD', '0'}, + {'00AB:0002:3008:8CFD:00AB:0002:3008:8CFD/64', '0'}, + {'0::0/64', '0'}, + {'0::0', '0'}, + }) +end + +function p:test_isIpV6Range() + self:preprocess_equals_many('{{#invoke:IPAddress/sandbox|isIpV6Range|', '}}', { + {'200.200.200.200', '0'}, + {'200.200.200.0/24', '0'}, + {'0.0.0.0', '0'}, + {'0.0.0.0/24', '0'}, + {'00AB:0002:3008:8CFD:00AB:0002:3008:8CFD', '0'}, + {'00AB:0002:3008:8CFD::/64', '1'}, + {'0::0/64', '1'}, + {'0::0', '0'}, + }) +end + +function p:test_temp_user() + self:preprocess_equals_many('{{#invoke:IPAddress/sandbox|isTempUser|', '}}', { + {'200.200.200.200', '0'}, + {'200.200.200.0/24', '0'}, + {'0.0.0.0', '0'}, + {'0.0.0.0/24', '0'}, + {'00AB:0002:3008:8CFD:00AB:0002:3008:8CFD', '0'}, + {'00AB:0002:3008:8CFD::/64', '0'}, + {'0::0/64', '0'}, + {'0::0', '0'}, + {'~2025-12345-67', '1'}, + {'2025-12345-67', '0'}, + {'Example', '0'} + }) +end + +function p:test_perm_user() + self:preprocess_equals_many('{{#invoke:IPAddress/sandbox|isPermUser|', '}}', { + {'200.200.200.200', '0'}, + {'200.200.200.0/24', '0'}, + {'0.0.0.0', '0'}, + {'0.0.0.0/24', '0'}, + {'00AB:0002:3008:8CFD:00AB:0002:3008:8CFD', '0'}, + {'00AB:0002:3008:8CFD::/64', '0'}, + {'0::0/64', '0'}, + {'0::0', '0'}, + {'~2025-12345-67', '0'}, + {'2025-12345-67', '1'}, + {'Example', '1'} + }) +end + +function p:test_user() + self:preprocess_equals_many('{{#invoke:IPAddress/sandbox|isUser|', '}}', { + {'200.200.200.200', ''}, + {'200.200.200.0/24', ''}, + {'0.0.0.0', ''}, + {'0.0.0.0/24', ''}, + {'00AB:0002:3008:8CFD:00AB:0002:3008:8CFD', ''}, + {'00AB:0002:3008:8CFD::/64', ''}, + {'0::0/64', ''}, + {'0::0', ''}, + {'~2025-12345-67', 'temp'}, + {'2025-12345-67', 'perm'}, + {'Example', 'perm'} + }) +end + +function p:test_main() + self:preprocess_equals_many('{{#invoke:IPAddress/sandbox|main|', '}}', { + {'200.200.200.200', 'ip'}, + {'200.200.200.0/24', 'range'}, + {'0.0.0.0', 'ip'}, + {'0.0.0.0/24', 'range'}, + {'00AB:0002:3008:8CFD:00AB:0002:3008:8CFD', 'ip'}, + {'00AB:0002:3008:8CFD::/64', 'range'}, + {'0::0/64', 'range'}, + {'0::0', 'ip'}, + {'~2025-12345-67', 'temp'}, + {'2025-12345-67', 'perm'}, + {'Example', 'perm'} + }) +end + +return p diff --git a/Testcases/ISO 639 name.lua b/Testcases/ISO 639 name.lua new file mode 100644 index 0000000..0a7d66e --- /dev/null +++ b/Testcases/ISO 639 name.lua @@ -0,0 +1,323 @@ +--[=[ +Unit tests for [[Module:ISO 639 name]] error messages. This is not intended to test every possible thing that +can make an error message; rather, it is to test the creation and rendering of the messages themselves. + +Click talk page to run tests. + +]=] + +local p = require('Module:UnitTests') + +--[[--------------------------< I S O _ 6 3 9 >---------------------------------------------------------------- +]] + +function p:test_iso_639() + self:preprocess_equals_preprocess_many( + '{{#invoke:ISO 639 name/sandbox|iso_639|cat=no|', '}}', '{{#invoke:ISO 639 name|iso_639|cat=no|', '}}', + { + {''}, -- no code param + {'xx'}, -- not a 639-1 code + {'nv'}, -- 639-1 code + {'cic'}, -- 639-2 code + {'ice'}, -- 639-2B code + {'nav'}, -- 639-3 code + {'apa'}, -- 639-5 code + + {'|'}, -- no code param, no 639-part param + {'xx|1'}, -- not a 639-1 code + {'nv|1'}, -- 639-1 code + {'nav|2'}, -- 639-2 code + {'ice|2B'}, -- 639-2B code + {'cic|3'}, -- 639-3 code + {'apa|5'}, -- 639-5 code + + {'apa|6'}, -- invalid 639-part + + {'el'}, -- 639-1 code in override table + {'ang'}, -- 639-2 code in override table + {'gre'}, -- 639-2B code in override table + {'egy'}, -- 639-3 code in override table +-- {''}, -- 639-5 code at this writing override table is empty + + {'iw'}, -- 639-1 code in deprecated table + {'mol'}, -- 639-2 code in deprecated table + {'jaw'}, -- 639-2B code in deprecated table + {'cqu'}, -- 639-3 code in deprecated table + {'car'}, -- 639-5 code in deprecated table (also in -2 and -3) + +--language names -- + {'Navajo'}, -- 639-1 name + {'Navajo'}, -- 639-2 name + {'Navaho'}, -- 639-3 name + {'Apache languages'}, -- 639-5 name + + {'Navajo|1'}, -- 639-1 name + {'Navajo|2'}, -- 639-2 name + {'Icelandic|2B'}, -- 639-2B name + {'Navaho|3'}, -- 639-3 name + {'Apache languages|5'}, -- 639-5 name + +-- {''}, -- name in deprecated 1; deprecated codes in 1 have same-name as other codes in 1 +-- {''}, -- name in deprecated 2; deprecated codes in 2 have same-name as other codes in 2 + {'Javanese|2B'}, -- name in deprecated 2B + {'Chilean Quechua|3'}, -- name in deprecated 3 + {'Galibi Carib|5'}, -- name in deprecated 5 + + {'Navajo|5'}, -- name not in 639-5 + {'Apache languages|3'}, -- name not in 639-3 + {'Chickasaw|2B'}, -- name not in 639-2B + {'Chickasaw|2'}, -- name not in 639-2 + {'Apache languages|1'}, -- name not in 639-1 + }, + {nowiki=false, templatestyles=true} + ) +end + + +--[[--------------------------< N A M E _ F R O M _ C O D E >-------------------------------------- + +cannot specify part + +]] + +function p:test_name_from_code() + self:preprocess_equals_preprocess_many( + '{{#invoke:ISO 639 name/sandbox|name_from_code|cat=no|', '}}', '{{#invoke:ISO 639 name|iso_639_code_to_name|cat=no|', '}}', + { + {''}, -- no language param + {'xx'}, -- not a 639-1 code + {'xxx'}, -- not a 639-2, 3, 5 code + {'nv'}, -- 639-1 code + {'nav'}, -- 639-2 code + {'nav'}, -- 639-3 code + {'apa'}, -- 639-5 code + + {'el'}, -- 639-1 code in override table + {'ang'}, -- 639-2 code in override table + {'tib'}, -- 639-2B code in override table + {'egy'}, -- 639-3 code in override table +-- {''}, -- 639-5 code at this writing override table is empty + + {'iw'}, -- 639-1 code in deprecated table + {'mol'}, -- 639-2 code in deprecated table + {'jaw'}, -- 639-2B code in deprecated table + {'cqu'}, -- 639-3 code in deprecated table + {'car'}, -- 639-5 code in deprecated table (also in -2 and -3) + +-- link & label + {'nv|link=yes'}, -- links to 639-1 name + {'nav|link=yes|label=Navaho'}, -- links to 639-2 name with alt spelling label + {'ice|link=yes|label=Frozen North'}, -- links to 639-2B name with alt spelling label + {'nav|label=Navaho'}, -- does not link; |label= is ignored + }, + {nowiki=false, templatestyles=true} + ) +end + + +--[[--------------------------< N A M E _ F R O M _ C O D E _ 1 >---------------------------------- +]] + +function p:test_name_from_code_1() + self:preprocess_equals_preprocess_many( + '{{#invoke:ISO 639 name/sandbox|name_from_code_1|cat=no|', '}}', '{{#invoke:ISO 639 name|iso_639_code_1_to_name|cat=no|', '}}', + { + {''}, -- no language param + {'xx'}, -- not a 639-1 code + {'xx|hide-err=yes'}, -- not a 639-1 code + {'nv-Latn'}, -- ietf tag + {'nv'}, -- 639-1 code + {'nav'}, -- 639-2 code + {'el'}, -- override 639-1 code + {'iw'}, -- 639-1 code in deprecated table + + {'nv|link=yes'}, -- linked + {'nv|link=yes|label=Navaho'}, -- linked with alt label + }, + {nowiki=false, templatestyles=true} + ) +end + + +--[[--------------------------< N A M E _ F R O M _ C O D E _ 2 >---------------------------------- +]] + +function p:test_name_from_code_2() + self:preprocess_equals_preprocess_many( + '{{#invoke:ISO 639 name/sandbox|name_from_code_2|cat=no|', '}}', '{{#invoke:ISO 639 name|iso_639_code_2_to_name|cat=no|', '}}', + { + {''}, -- no language param + {'xxx'}, -- not a 639-2 code + {'xxx|hide-err=yes'}, -- not a 639-2 code + {'nav-Latn'}, -- ietf tag + {'nv'}, -- 639-1 code + {'nav'}, -- 639-2 code + {'ice'}, -- 639-2B code + {'mga'}, -- override 639-2 code + {'mol'}, -- 639-2 code in deprecated table + + {'nav|link=yes'}, -- linked + {'nav|link=yes|label=Navaho'}, -- linked with alt label + }, + {nowiki=false, templatestyles=true} + ) +end + + +--[[--------------------------< N A M E _ F R O M _ C O D E _ 2 B >-------------------------------- +]] + +function p:test_name_from_code_2B() + self:preprocess_equals_preprocess_many( + '{{#invoke:ISO 639 name/sandbox|name_from_code_2B|cat=no|', '}}', '{{#invoke:ISO 639 name|iso_639_code_2B_to_name|cat=no|', '}}', + { + {''}, -- no language param + {'xxx'}, -- not a 639-2 code + {'xxx|hide-err=yes'}, -- not a 639-2 code + {'nav-Latn'}, -- ietf tag + {'nv'}, -- 639-1 code + {'nav'}, -- 639-2T code + {'ice'}, -- 639-2B code + {'tib'}, -- 639-2B code in override table + {'jaw'}, -- 639-2B code in deprecated table + + {'ice|link=yes'}, -- linked + {'ice|link=yes|label=Frozen North'}, -- linked with alt label + }, + {nowiki=false, templatestyles=true} + ) +end + + +--[[--------------------------< N A M E _ F R O M _ C O D E _ 3 >---------------------------------- +]] + +function p:test_name_from_code_3() + self:preprocess_equals_preprocess_many( + '{{#invoke:ISO 639 name/sandbox|name_from_code_3|cat=no|', '}}', '{{#invoke:ISO 639 name|iso_639_code_3_to_name|cat=no|', '}}', + { + {''}, -- no language param + {'xxx'}, -- not a 639-3 code + {'xxx|hide-err=yes'}, -- not a 639-3 code + {'nav-Latn'}, -- ietf tag + {'nv'}, -- 639-1 code + {'nav'}, -- 639-3 code + {'pms'}, -- override 639-3 code + {'cqu'}, -- 639-3 code in deprecated table + + {'nav|link=yes'}, -- linked + {'nav|link=yes|label=Navajo'}, -- linked with alt label + }, + {nowiki=false, templatestyles=true} + ) +end + + +--[[--------------------------< N A M E _ F R O M _ C O D E _ 5 >---------------------------------- +]] + +function p:test_name_from_code_5() + self:preprocess_equals_preprocess_many( + '{{#invoke:ISO 639 name/sandbox|name_from_code_5|cat=no|', '}}', '{{#invoke:ISO 639 name|iso_639_code_5_to_name|cat=no|', '}}', + { + {''}, -- no language param + {'xxx'}, -- not a 639-5 code + {'xxx|hide-err=yes'}, -- not a 639-5 code + {'nav-Latn'}, -- ietf tag + {'nv'}, -- 639-1 code + {'apa'}, -- 639-5 code +-- {''}, -- override 639-5 code; none at this writing + {'car'}, -- 639-5 code in deprecated table (also in -2 and -3) + + {'apa|link=yes'}, -- linked + {'apa|link=yes|label=Apache'}, -- linked with alt label + }, + {nowiki=false, templatestyles=true} + ) +end + + +--[[--------------------------< C O D E _ F R O M _ N A M E >-------------------------------------- +]] + +function p:test_code_from_name() + self:preprocess_equals_preprocess_many( + '{{#invoke:ISO 639 name/sandbox|code_from_name|cat=no|', '}}', '{{#invoke:ISO 639 name|iso_639_name_to_code|cat=no|', '}}', + { + {''}, -- no name param + {'nv'}, -- 639-1 code +--language names -- + {'Navajo'}, -- 639-1 name + {'Navajo'}, -- 639-2 name +-- {''}, -- 639-2B names same as -1, -2T names so -1 name will be used for this test + {'Navaho'}, -- 639-3 name + {'Apache languages'}, -- 639-5 name + + {'Navajo|1'}, -- 639-1 name + {'Icelandic|2'}, -- 639-2 name + {'Icelandic|2B'}, -- 639-2B name + {'Navaho|3'}, -- 639-3 name + {'Apache languages|5'}, -- 639-5 name + + {'Navajo|5'}, -- 639-1 name not in 5 + {'Navajo|3'}, -- 639-2 name not in 3 + {'Navaho|2B'}, -- 639-3 name not in 2B + {'Navaho|2'}, -- 639-3 name not in 2 + {'Apache languages|1'}, -- 639-5 name not in 1 + + {'Apache languages|6'}, -- invalid 639-part + + {'Greek'}, -- only 639-1 code in override table + {'Old English'}, -- 639-2 code in override table + {'Standard Tibetan'}, -- 639-2B code in override table; same as -1 and -2 so -1 code is returned + {'Ancient Egyptian'}, -- 639-3 code in override table +-- {''}, -- 639-5 code at this writing override table is empty + +-- {''}, -- name in deprecated 1; deprecated codes in 1 have same-name as other codes in 1 +-- {''}, -- name in deprecated 2; deprecated codes in 2 have same-name as other codes in 2 + {'Javanese|2B'}, -- name in deprecated 2B + {'Chilean Quechua|3'}, -- name in deprecated 3 + {'Galibi Carib|5'}, -- name in deprecated 5 + +-- these should not link + {'Navajo|link=yes'}, -- 639-1 name + {'Navajo|link=yes|label=Navajo'}, -- 639-2 name + {'Navaho|label=Navajo'}, -- 639-3 name + }, + {nowiki=false, templatestyles=true} + ) +end + + +--[[--------------------------< I S _ C O D E >---------------------------------------- +]] + +function p:test_is_code() + self:preprocess_equals_preprocess_many( + '{{#invoke:ISO 639 name/sandbox|is_code|cat=no|', '}}', '{{#invoke:ISO 639 name|iso_639_code_exists|cat=no|', '}}', + { + {''}, -- no code param + {'xx'}, -- not a 639-1 code + {'xxx'}, -- not a 639-2, 3, 5 code + + {'nv'}, -- 639-1 code + {'apa'}, -- 639-2 code + {'ice'}, -- 639-2B code + {'nav'}, -- 639-2 code + + {'el'}, -- override 639-1 code + {'mga'}, -- override 639-2 code + {'pms'}, -- override 639-3 code + + {'iw'}, -- 639-1 code in deprecated table + {'mol'}, -- 639-2 code in deprecated table + {'jaw'}, -- 639-2B code in deprecated table + {'cqu'}, -- 639-3 code in deprecated table + {'car'}, -- 639-5 code in deprecated table (also in -2 and -3) + }, + {nowiki=false, templatestyles=true} + ) +end + + +return p