diff options
Diffstat (limited to 'tests/config/basic.test.lua')
-rw-r--r-- | tests/config/basic.test.lua | 211 |
1 files changed, 211 insertions, 0 deletions
diff --git a/tests/config/basic.test.lua b/tests/config/basic.test.lua new file mode 100644 index 0000000..73356a4 --- /dev/null +++ b/tests/config/basic.test.lua @@ -0,0 +1,211 @@ +-- SPDX-License-Identifier: GPL-3.0-or-later +local ffi = require('ffi') + +-- test if constants work properly +local function test_constants() + same(kres.class.IN, 1, 'class constants work') + same(kres.type.NS, 2, 'record type constants work') + same(kres.type.TYPE2, 2, 'unnamed record type constants work') + same(kres.type.BADTYPE, nil, 'non-existent type constants are checked') + same(kres.section.ANSWER, 0, 'section constants work') + same(kres.rcode.SERVFAIL, 2, 'rcode constants work') + same(kres.opcode.UPDATE, 5, 'opcode constants work') + -- Test inverse tables to convert constants to text + same(kres.tostring.class[1], 'IN', 'text class constants work') + same(kres.tostring.type[2], 'NS', 'text record type constants work') + same(kres.tostring.type[65535], 'TYPE65535', 'text record type undefined constants work') + same(kres.tostring.section[0], 'ANSWER', 'text section constants work') + same(kres.tostring.rcode[2], 'SERVFAIL', 'text rcode constants work') + same(kres.tostring.opcode[5], 'UPDATE', 'text opcode constants work') +end + +-- test globals +local function test_globals() + ok(mode('strict'), 'changing strictness mode') + boom(mode, {'badmode'}, 'changing to non-existent strictness mode') + same(reorder_RR(true), true, 'answer section reordering') + same(option('REORDER_RR', false), false, 'generic option call') + boom(option, {'REORDER_RR', 'potato'}, 'generic option call argument check') + boom(option, {'MARS_VACATION', false}, 'generic option check name') + same(table_print('crabdiary'), "'crabdiary'", 'table print works') + same(table_print({fakepizza=1}), "{\n ['fakepizza'] = 1,\n}", 'table print works on tables') +end + +-- test global API functions +local function test_global_functions() + boom(kres.parse_rdata, {'A 127.0.0.1'}, 'parse_rdata with non-table argument') + boom(kres.parse_rdata, {{1}}, 'parse_rdata with non-string data in arg table') + same(kres.parse_rdata({'A 127.0.0.1'}), {string.char(127, 0, 0, 1)}, 'parse_rdata with single record') + same(kres.parse_rdata({'A 127.0.0.1', 'A 127.0.0.2'}), + {string.char(127, 0, 0, 1), string.char(127, 0, 0, 2)}, 'parse_rdata with multiple records') +end + +-- test if dns library functions work +local function test_rrset_functions() + local rr = {owner = '\3com\0', ttl = 1, type = kres.type.TXT, rdata = '\5hello'} + local rr_text = tostring(kres.rr2str(rr)) + same(rr_text:gsub('%s+', ' '), 'com. 1 TXT "hello"', 'rrset to text works') + same(kres.dname2str(todname('com.')), 'com.', 'domain name conversion works') + -- test creating rrset + rr = kres.rrset(todname('com.'), kres.type.A, kres.class.IN, 66) + ok(ffi.istype(kres.rrset, rr), 'created an empty RR') + same(rr:owner(), '\3com\0', 'created RR has correct owner') + same(rr:class(), kres.class.IN, 'created RR has correct class') + same(rr:class(kres.class.CH), kres.class.CH, 'can set a different class') + same(rr:class(kres.class.IN), kres.class.IN, 'can restore a class') + same(rr.type, kres.type.A, 'created RR has correct type') + -- test adding rdata + same(rr:wire_size(), 0, 'empty RR wire size is zero') + ok(rr:add_rdata('\1\2\3\4', 4), 'adding RDATA works') + same(rr:wire_size(), 5 + 4 + 4 + 2 + 4, 'RR wire size works after adding RDATA') + -- test conversion to text + local expect = 'com. 66 A 1.2.3.4\n' + same(rr:txt_dump(), expect, 'RR to text works') + -- create a dummy rrsig + local rrsig = kres.rrset(todname('com.'), kres.type.RRSIG, kres.class.IN, 0) + rrsig:add_rdata('\0\1', 2) + same(rr:rdcount(), 1, 'add_rdata really added RDATA') + -- check rrsig matching + same(rr.type, rrsig:type_covered(), 'rrsig type covered matches covered RR type') + ok(rr:is_covered_by(rrsig), 'rrsig is covering a record') + -- test rrset merging + local copy = kres.rrset(rr:owner(), rr.type, kres.class.IN, 66) + ok(copy:add_rdata('\4\3\2\1', 4), 'adding second RDATA works') + ok(rr:merge_rdata(copy), 'merge_rdata works') + same(rr:rdcount(), 2, 'RDATA count is correct after merge_rdata') + expect = 'com. 66 A 1.2.3.4\n' .. + 'com. 66 A 4.3.2.1\n' + same(rr:txt_dump(), expect, 'merge_rdata actually merged RDATA') +end + +-- test dns library packet interface +local function test_packet_functions() + local pkt = kres.packet(512) + isnt(pkt, nil, 'creating packets works') + -- Test manipulating header + ok(pkt:rcode(kres.rcode.NOERROR), 'setting rcode works') + same(pkt:rcode(), 0, 'getting rcode works') + same(pkt:opcode(), 0, 'getting opcode works') + is(pkt:aa(), false, 'packet is created without AA') + is(pkt:ra(), false, 'packet is created without RA') + is(pkt:ad(), false, 'packet is created without AD') + ok(pkt:rd(true), 'setting RD bit works') + is(pkt:rd(), true, 'getting RD bit works') + ok(pkt:tc(true), 'setting TC bit works') + is(pkt:tc(), true, 'getting TC bit works') + ok(pkt:tc(false), 'disabling TC bit works') + is(pkt:tc(), false, 'getting TC bit after disable works') + is(pkt:cd(), false, 'getting CD bit works') + is(pkt:id(1234), 1234, 'setting MSGID works') + is(pkt:id(), 1234, 'getting MSGID works') + -- Test manipulating question + is(pkt:qname(), nil, 'reading name from empty question') + is(pkt:qtype(), 0, 'reading type from empty question') + is(pkt:qclass(), 0, 'reading class from empty question') + ok(pkt:question(todname('hello'), kres.class.IN, kres.type.A), 'setting question section works') + same(pkt:qname(), todname('hello'), 'reading QNAME works') + same(pkt:qtype(), kres.type.A, 'reading QTYPE works') + same(pkt:qclass(), kres.class.IN, 'reading QCLASS works') + -- Test manipulating sections + ok(pkt:begin(kres.section.ANSWER), 'switching sections works') + local res, err = pkt:put(nil, 0, 0, 0, '') + isnt(res, true, 'inserting nil entry doesnt work') + isnt(err.code, 0, 'error code is non-zero') + isnt(tostring(res), '', 'inserting nil returns invalid parameter') + ok(pkt:put(pkt:qname(), 900, pkt:qclass(), kres.type.A, '\1\2\3\4'), 'adding rrsets works') + boom(pkt.begin, {pkt, 10}, 'switching to invalid section doesnt work') + ok(pkt:begin(kres.section.ADDITIONAL), 'switching to different section works') + boom(pkt.begin, {pkt, 0}, 'rewinding sections doesnt work') + local before_insert = pkt:remaining_bytes() + ok(pkt:put(pkt:qname(), 900, pkt:qclass(), kres.type.A, '\4\3\2\1'), 'adding rrsets to different section works') + same(pkt:remaining_bytes(), before_insert - (2 + 4 + 4 + 2 + 4), 'remaining bytes count goes down with insertions') + -- Test conversions to text + like(pkt:tostring(), '->>HEADER<<-', 'packet to text works') + -- Test deserialization + local wire = pkt:towire() + same(#wire, 55, 'packet serialization works') + local parsed = kres.packet(#wire, wire) + isnt(parsed, nil, 'creating packet from wire works') + ok(parsed:parse(), 'parsing packet from wire works') + same(parsed:qname(), pkt:qname(), 'parsed packet has same QNAME') + same(parsed:qtype(), pkt:qtype(), 'parsed packet has same QTYPE') + same(parsed:qclass(), pkt:qclass(), 'parsed packet has same QCLASS') + same(parsed:opcode(), pkt:opcode(), 'parsed packet has same opcode') + same(parsed:rcode(), pkt:rcode(), 'parsed packet has same rcode') + same(parsed:rd(), pkt:rd(), 'parsed packet has same RD') + same(parsed:id(), pkt:id(), 'parsed packet has same MSGID') + same(parsed:qdcount(), pkt:qdcount(), 'parsed packet has same question count') + same(parsed:ancount(), pkt:ancount(), 'parsed packet has same answer count') + same(parsed:nscount(), pkt:nscount(), 'parsed packet has same authority count') + same(parsed:arcount(), pkt:arcount(), 'parsed packet has same additional count') + same(parsed:tostring(), pkt:tostring(), 'parsed packet is equal to source packet') + + -- Test adding RR sets directly + local copy = kres.packet(512) + copy:question(todname('hello'), kres.class.IN, kres.type.A) + copy:begin(kres.section.ANSWER) + local rr = kres.rrset(pkt:qname(), kres.type.A, kres.class.IN, 66) + rr:add_rdata('\4\3\2\1', 4) + ok(copy:put_rr(rr), 'adding RR sets directly works') + ok(copy:recycle(), 'recycling packet works') + + -- Test recycling of packets + -- Clear_payload keeps header + question intact + local cleared = kres.packet(#wire, wire) -- same as "parsed" above + ok(cleared:parse(), 'parsing packet from wire works') + ok(cleared:clear_payload(), 'clear_payload works') + same(cleared:id(), pkt:id(), 'cleared packet has same MSGID') + same(cleared:qr(), pkt:qr(), 'cleared packet has same QR') + same(cleared:opcode(), pkt:opcode(), 'cleared packet has same OPCODE') + same(cleared:aa(), pkt:aa(), 'cleared packet has same AA') + same(cleared:tc(), pkt:tc(), 'cleared packet has same TC') + same(cleared:rd(), pkt:rd(), 'cleared packet has same RD') + same(cleared:ra(), pkt:ra(), 'cleared packet has same RA') + same(cleared:ad(), pkt:ad(), 'cleared packet has same AD') + same(cleared:cd(), pkt:cd(), 'cleared packet has same CD') + same(cleared:rcode(), pkt:rcode(), 'cleared packet has same RCODE') + same(cleared:qdcount(), pkt:qdcount(), 'cleared packet has same question count') + same(cleared:ancount(), 0, 'cleared packet has no answers') + same(cleared:nscount(), 0, 'cleared packet has no authority') + same(cleared:arcount(), 0, 'cleared packet has no additional') + same(cleared:qname(), pkt:qname(), 'cleared packet has same QNAME') + same(cleared:qtype(), pkt:qtype(), 'cleared packet has same QTYPE') + same(cleared:qclass(), pkt:qclass(), 'cleared packet has same QCLASS') + + -- Recycle clears question as well + ok(pkt:recycle(), 'recycle() works') + is(pkt:ancount(), 0, 'recycle() clears records') + is(pkt:qname(), nil, 'recycle() clears question') + is(#pkt:towire(), 12, 'recycle() clears the packet wireformat') +end + +-- test JSON encode/decode functions +local function test_json_functions() + for msg, obj in pairs({ + ['number'] = 0, + ['string'] = 'ok', + ['list'] = {1, 2, 3}, + ['map'] = {foo='bar'}, + ['nest structure'] = {foo='bar', baz={1,2,3}}, + }) do + same(fromjson(tojson(obj)), obj, 'json test: ' .. msg) + end + + for _, str in ipairs({ + '{', '}', + '[', ']', + 'x,', + '[1,2,3,]', + }) do + boom(fromjson, {'{'}, 'json test: invalid \'' .. str .. '\'') + end +end + +return { + test_constants, + test_globals, + test_global_functions, + test_rrset_functions, + test_packet_functions, + test_json_functions, +} |