amazon-route-53-dns-zone-file
Version:
Makes DNS Zone File easy. Parses and validates BIND zone files and can be extended for custom features. Functionality is modular. Features are made open for extension and closed for runtime mutation. Written in TypeScript.
1,443 lines (1,404 loc) • 38.2 kB
text/typescript
import {
SUPPORTED_RECORD_TYPES,
RecordMinLengthKind,
ErrorKeyKind,
WarningKeyKind,
ParserInvalidArgumentsError,
ParsingError,
} from '../../src';
import { ExampleDomain } from '../../example/example_types';
import { getErrorText } from '../../example/example_consumer';
import * as complexMock from '../mocks/complex';
import * as validMock from '../mocks/valid';
import * as invalidApiMock from '../mocks/invalid_api';
import * as invalidClientMock from '../mocks/invalid_client';
import * as edgeMock from '../mocks/edge';
import {
testImportZoneFileAndCallApi,
testImportZoneFile,
spyOnCreateApi,
MockSupportedRecordType,
} from '../utils';
type MockWithoutTypeForKey<Key extends string> = {
[K in Key]?: ExampleDomain.ClientRecord[];
};
type MockTypeForKey<Key extends string> = {
[K in MockSupportedRecordType]: MockWithoutTypeForKey<Key>;
};
type IValidMockKeyType = keyof typeof validMock;
type IInvalidApiMockKeyType = keyof typeof invalidApiMock;
type IComplexMockKeyType = keyof typeof complexMock;
describe('Import zone file (features)', () => {
const validTestCases: MockTypeForKey<IValidMockKeyType> = {
GENERAL: {
ttlWithNameSimilarToTTL: [
{
name: '100.canada.igloo.com.',
endpoints: ['2.3.4.5'],
type: 'A',
ttl: 42,
},
],
backwardsTTL: [
{
name: 'eh.igloo.com.',
endpoints: ['2.3.4.5'],
type: 'A',
ttl: 42,
},
],
optionalTTLAndClass: [
{
name: 'neither.igloo.com.',
endpoints: ['1.2.3.4'],
type: 'A',
ttl: 4242,
},
{
name: 'ttl.igloo.com.',
endpoints: ['1.2.3.4'],
type: 'A',
ttl: 42,
},
{
name: 'class.igloo.com.',
endpoints: ['1.2.3.4'],
type: 'A',
ttl: 4242,
},
{
name: 'ttl.class.igloo.com.',
endpoints: ['1.2.3.4'],
type: 'A',
ttl: 42,
},
{
name: 'class.ttl.igloo.com.',
endpoints: ['1.2.3.4'],
type: 'A',
ttl: 42,
},
],
comments1: [
{
name: 'eh.igloo.com.',
endpoints: ['2.3.4.5'],
type: 'A',
ttl: 42,
},
{
name: 'www.igloo.com.',
endpoints: ['1.2.3.4'],
type: 'A',
ttl: 42,
},
],
nameNotTooLong: [
{
name:
'1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.igloo.com.',
endpoints: ['1.2.3.4'],
ttl: 60,
type: 'A',
},
],
utfCharacters: [
{
name: '©.igloo.com.',
endpoints: ['1.2.3.4'],
ttl: 42,
type: 'A',
},
{
name: '¿.igloo.com.',
endpoints: ['1.2.3.4'],
ttl: 42,
type: 'A',
},
],
escapeInTXT: [
{
name: 'www.igloo.com.',
endpoints: ['"\\101\\"'],
ttl: 42,
type: 'TXT',
},
],
escapeInCNAMEValue: [
{
name: 'www.igloo.com.',
endpoints: ['\\008\\99.igloo.com.'],
ttl: 42,
type: 'CNAME',
},
],
defaultOrigin: [
{ name: 'igloo.com.', endpoints: ['1.2.3.4'], ttl: 42, type: 'A' },
{
name: 'www.igloo.com.',
endpoints: ['1.2.3.4'],
ttl: 42,
type: 'A',
},
],
specialTTL: [
{
name: 'eh.igloo.com.',
endpoints: ['2.3.4.5'],
ttl: 42,
type: 'A',
},
],
},
AAAA: {
aaaaWithNameAndTopLevelTTL: [
{
name: 'example.igloo.com.',
endpoints: ['::1'],
ttl: 42,
type: 'AAAA',
},
],
aaaaWithAtNameAndInlineTTL: [
{
name: 'igloo.com.',
endpoints: ['::1'],
ttl: 42,
type: 'AAAA',
},
],
},
A: {
aWithNameAndTopLevelTTL: [
{
name: 'example.igloo.com.',
endpoints: ['127.0.0.1'],
ttl: 42,
type: 'A',
},
],
aWithAtNameAndInlineTTL: [
{
name: 'igloo.com.',
endpoints: ['127.0.0.1'],
ttl: 42,
type: 'A',
},
],
},
CAA: {
caaExampleJustIssueWithInNoNameTopLevelTTL: [
{
name: 'igloo.com.',
endpoints: ['0 issue "ca.igloo.com"'],
ttl: 42,
type: 'CAA',
},
],
caaExampleJustIssueNoNameTopLevelTTL: [
{
name: 'igloo.com.',
endpoints: ['0 issue "ca.igloo.com"'],
ttl: 42,
type: 'CAA',
},
],
caaExampleIssueAndIoDefAndTopLevelTTL: [
{
name: 'igloo.com.',
endpoints: [
'0 issue "ca.igloo.com"',
'0 iodef "mailto:security@igloo.com"',
'0 iodef "http://iodef.igloo.com/"',
],
ttl: 42,
type: 'CAA',
},
],
caaExampleIssueAndIoDefAndTopLevelTTLNoQuotes: [
{
name: 'igloo.com.',
endpoints: [
'0 issue ca.igloo.com',
'0 iodef mailto:security@igloo.com',
'0 iodef http://iodef.igloo.com/',
],
ttl: 42,
type: 'CAA',
},
],
caaExampleJustIoDef: [
{
name: 'igloo.com.',
endpoints: ['0 iodef "mailto:security@igloo.com"'],
type: 'CAA',
ttl: 42,
},
],
caaExampleInlineTTL: [
{
name: 'igloo.com.',
endpoints: [
'0 issue "ca.igloo.com"',
'0 iodef "mailto:security@igloo.com"',
],
ttl: 42,
type: 'CAA',
},
],
caaExampleJustIssue: [
{
name: 'igloo.com.',
endpoints: ['0 issue "ca.igloo.com"'],
ttl: 42,
type: 'CAA',
},
],
},
MX: {
mxExampleAt: [
{
name: 'igloo.com.',
endpoints: ['0 mail1', '10 mail2'],
ttl: 42,
type: 'MX',
},
],
},
CNAME: {
cnameWithTopLevelTTL: [
{
name: 'mail1.igloo.com.',
endpoints: ['mail'],
ttl: 42,
type: 'CNAME',
},
{
name: 'mail2.igloo.com.',
endpoints: ['mail'],
ttl: 42,
type: 'CNAME',
},
],
cnameWithInlineAndSpacesTTL: [
{
name: 'mail1.igloo.com.',
endpoints: ['mail'],
ttl: 42,
type: 'CNAME',
},
{
name: 'mail2.igloo.com.',
endpoints: ['mail'],
ttl: 60,
type: 'CNAME',
},
],
},
NAPTR: {
naptrExampleTopLevelTTL: [
{
name: 'igloo.com.',
endpoints: [
'100 10 "U" "E2U+sip" "!^.*$!sip:customer-service@example.com!" .',
'102 10 "U" "E2U+email" "!^.*$!mailto:information@example.com!" .',
],
ttl: 42,
type: 'NAPTR',
},
],
naptrExample1: [
{
name: 'rfc3403-1.igloo.com.',
endpoints: [
'100 10 "u" "sip+E2U" "!^.*$!sip:information@foo.se!i" .',
],
ttl: 42,
type: 'NAPTR',
},
{
name: 'rfc3403-2.igloo.com.',
endpoints: ['100 50 "a" "z3950+N2L+N2C" "" cidserver.igloo.com.'],
ttl: 42,
type: 'NAPTR',
},
],
naptrExampleInlineTTLAndNested: [
{
name: 'rfc3403-1.igloo.com.',
endpoints: [
'100 10 "u" "sip+E2U" "!^.*$!sip:information@foo.se!i" .',
],
ttl: 42,
type: 'NAPTR',
},
{
name: 'rfc3403-2.igloo.com.',
endpoints: ['100 50 "a" "z3950+N2L+N2C" "" cidserver.igloo.com.'],
ttl: 42,
type: 'NAPTR',
},
{
name: 'igloo.com.',
endpoints: [
'99 98 "B" "E2U+siz" "" .',
'100 10 "U" "E2U+sip" "!^.*$!sip:customer-service@example.com!" .',
'102 10 "U" "E2U+email" "!^.*$!mailto:information@example.com!" .',
],
ttl: 42,
type: 'NAPTR',
},
],
},
TXT: {
txtExampleTopLevelTTL: [
{
name: 'treefrog.igloo.com.',
endpoints: [
'"v=spf1 a mx a:mail.treefrog.ca a:webmail.treefrog.ca ip4:76.75.250.33 ?all"',
],
ttl: 42,
type: 'TXT',
},
{
name: 'treemonkey.igloo.com.',
endpoints: ['"v=DKIM1; k=rsa; p=MIGf..."'],
ttl: 42,
type: 'TXT',
},
{
name: 'treemonkey1.igloo.com.',
endpoints: ['"v=DKIM1; k=rsa; p=MIGf..."'],
ttl: 42,
type: 'TXT',
},
],
txtExampleInlineTTL: [
{
name: 'treefrog.igloo.com.',
endpoints: [
'"v=spf1 a mx a:mail.treefrog.ca a:webmail.treefrog.ca ip4:76.75.250.33 ?all"',
],
ttl: 42,
type: 'TXT',
},
{
name: 'treemonkey.igloo.com.',
endpoints: ['"v=DKIM1; k=rsa; p=MIGf..."'],
ttl: 42,
type: 'TXT',
},
{
name: 'treemonkey1.igloo.com.',
endpoints: ['"v=DKIM1; k=rsa; p=MIGf..."'],
ttl: 42,
type: 'TXT',
},
],
/** @idea would we want to join in this case? */
txtExampleNested: [
{
name: 'treefrog.igloo.com.',
endpoints: ['"eh=true"', '"moose=DKIM1; beaver=canada;"'],
ttl: 42,
type: 'TXT',
},
],
parensInTXT: [
{
name: 'eh.igloo.com.',
endpoints: ['"Some text (Moose)"', '"spaced ( spaces1 spaces2 )"'],
ttl: 42,
type: 'TXT',
},
],
},
NS: {
nsExampleWithAtAndTopLevelTTL: [],
nsExampleWithNoNameAndTopLevelTTL: [],
nsExampleWithNamesAndTopLevelTTL: [
{
name: 'syrup.igloo.com.',
endpoints: ['NS1.NAMESERVER.NET.'],
ttl: 42,
type: 'NS',
},
{
name: 'beaver.igloo.com.',
endpoints: ['NS2.NAMESERVER.NET.'],
type: 'NS',
ttl: 42,
},
],
nsExampleInlineTTL: [
{
name: 'eh.igloo.com.',
endpoints: ['NS1.NAMESERVER.NET.'],
ttl: 42,
type: 'NS',
},
{
name: 'canada.igloo.com.',
endpoints: ['NS2.NAMESERVER.NET.'],
ttl: 42,
type: 'NS',
},
],
nsExampleNestedWithTopLevelTTL: [
{
name: 'moose.igloo.com.',
endpoints: ['NS1.NAMESERVER.NET.', 'NS2.NAMESERVER.NET.'],
ttl: 42,
type: 'NS',
},
],
},
PTR: {
ptrExampleOneEntryTopLevelTTL: [
{
endpoints: ['HOST1.igloo.com.'],
name: '99.11.igloo.com.',
type: 'PTR',
ttl: 42,
},
],
ptrExampleInlineTTL: [
{
name: '1.0.168.192.igloo.com.',
endpoints: ['HOST1.igloo.com.'],
type: 'PTR',
ttl: 42,
},
{
name: '2.0.168.192.igloo.com.',
endpoints: ['HOST2.igloo.com.'],
type: 'PTR',
ttl: 42,
},
],
ptrExampleTopLevelTTL: [
{
name: '1.0.168.192.igloo.com.',
endpoints: ['HOST1.igloo.com.'],
type: 'PTR',
ttl: 42,
},
{
name: '2.0.168.192.igloo.com.',
endpoints: ['HOST2.igloo.com.'],
type: 'PTR',
ttl: 42,
},
],
ptrExampleNestedTopLevelTTL: [
{
name: '3.30.168.192.igloo.com',
endpoints: ['HOST3.igloo.com.'],
type: 'PTR',
ttl: 42,
},
{
name: '4.30.168.192.igloo.com',
endpoints: [
'HOST4.igloo.com.',
'HOST5.igloo.com.',
'HOST6.igloo.com.',
],
type: 'PTR',
ttl: 42,
},
],
ptrExampleDecimals: [
{
endpoints: ['HOST3.igloo.com.'],
name: '10.3.168.192.igloo.com',
ttl: 42,
type: 'PTR',
},
{
endpoints: ['HOST4.igloo.com.'],
name: '10.4.168.192.igloo.com',
ttl: 42,
type: 'PTR',
},
],
ptrMultipleOrigins: [
{
endpoints: ['HOST3.igloo.com.'],
name: '3.30.168.192.igloo.com',
ttl: 41,
type: 'PTR',
},
{
endpoints: [
'HOST4.igloo.com.',
'HOST5.igloo.com.',
'HOST6.igloo.com.',
],
name: '4.30.168.192.igloo.com',
ttl: 42,
type: 'PTR',
},
{
endpoints: ['HOST3.igloo.com.'],
name: '10.3.168.192.igloo.com',
ttl: 45,
type: 'PTR',
},
{
endpoints: ['HOST4.igloo.com.'],
name: '10.4.168.192.igloo.com',
ttl: 46,
type: 'PTR',
},
],
},
SPF: {
spfMaxValues: [
{
name: 'eh.igloo.com.',
endpoints: ['v=spf1 ip4:192.0.2.0/24 ip4:198.51.100.123 a -all'],
ttl: 42,
type: 'SPF',
},
],
spfMinValues: [
{
name: 'eh.igloo.com.',
endpoints: ['v=spf1 ip:4:192.168.0.1/16-all'],
ttl: 42,
type: 'SPF',
},
],
spfExampleOneEntryInlineTTL: [
{
name: 'test.igloo.com.',
endpoints: ['"v=spf1" "mx:gcloud-node.com." "-all"'],
ttl: 42,
type: 'SPF',
},
],
spfExampleNestedTopLevelTTL: [
{
name: 'test.igloo.com.',
endpoints: ['"v=spf1" "mx:gcloud-node.com." "-all"'],
ttl: 42,
type: 'SPF',
},
{
name: 'test1.igloo.com.',
endpoints: [
'"v=spf2" "mx:gcloud-node.com." "-all"',
'"v=spf3" "mx:gcloud-node.com." "-all "',
'"v=spf4" "mx:gcloud-node.com." "-all"',
],
ttl: 42,
type: 'SPF',
},
],
spfExampleNestedInlineTTL: [
{
name: 'test.igloo.com.',
endpoints: ['"v=spf1" "mx:gcloud-node.com." "-all"'],
ttl: 42,
type: 'SPF',
},
{
name: 'test1.igloo.com.',
endpoints: [
'"v=spf2" "mx:gcloud-node.com." "-all"',
'"v=spf3" "mx:gcloud-node.com." "-all "',
'"v=spf4" "mx:gcloud-node.com." "-all"',
],
ttl: 42,
type: 'SPF',
},
],
spfExampleAtNameInlineTTL: [
{
name: 'igloo.com.',
endpoints: ['"v=spf1 a mx ~all"'],
ttl: 42,
type: 'TXT',
},
],
spfExampleOfOneParam: [
{
name: 'eh.igloo.com.',
endpoints: ['"v=spf1 ip:4:192.168.0.1/16"'],
ttl: 42,
type: 'SPF',
},
],
},
SRV: {
srvExampleWithNameAndTopLevelTTL: [
{
name: 'server.igloo.com.',
endpoints: ['172.30.79.10'],
ttl: 42,
type: 'A',
},
{
name: 'old-slow-box.igloo.com.',
endpoints: ['172.30.79.11'],
ttl: 42,
type: 'A',
},
{
name: 'sysadmins-box.igloo.com.',
endpoints: ['172.30.79.12'],
ttl: 42,
type: 'A',
},
{
name: 'new-fast-box.igloo.com.',
endpoints: ['172.30.79.13'],
ttl: 42,
type: 'A',
},
{
name: '_foobar._tcp.igloo.com.',
endpoints: [
'0 1 9 old-slow-box.igloo.com.',
'0 3 9 new-fast-box.igloo.com.',
'1 0 9 sysadmins-box.igloo.com.',
'1 0 9 server.igloo.com.',
],
ttl: 42,
type: 'SRV',
},
{
name: '*._tcp.igloo.com.',
endpoints: ['0 0 0 .'],
ttl: 42,
type: 'SRV',
},
{
name: '*._udp.igloo.com.',
endpoints: ['0 0 0 .'],
ttl: 42,
type: 'SRV',
},
],
srvExampleWithNameAndInlineTTL: [
{
name: '_foobar._tcp.igloo.com.',
endpoints: [
'0 1 9 old-slow-box.igloo.com.',
'0 3 9 new-fast-box.igloo.com.',
'1 0 9 sysadmins-box.igloo.com.',
'1 0 9 server.igloo.com.',
],
ttl: 42,
type: 'SRV',
},
{
name: '*._tcp.igloo.com.',
endpoints: ['0 0 0 .'],
ttl: 42,
type: 'SRV',
},
{
name: '*._udp.igloo.com.',
endpoints: ['0 0 0 .'],
ttl: 42,
type: 'SRV',
},
],
},
SOA: {
soaIsIgnored1: [],
soaIsIgnored2: [
{
name: 'example.igloo.com.',
endpoints: ['127.0.0.1'],
/** @default 300 */
ttl: 300,
type: 'A',
},
],
soaIsIgnored3: [
{
name: 'mail1.igloo.com.',
endpoints: ['mail'],
/** @default 300 */
ttl: 300,
type: 'CNAME',
},
],
simpleSoa: [],
},
};
describe.each([
'GENERAL',
...SUPPORTED_RECORD_TYPES,
'SOA',
] as MockSupportedRecordType[])(
'Parsing type "%s" (valid)',
resourceRecordType => {
beforeEach(() => {
spyOnCreateApi().mockImplementation(() => Promise.resolve(true));
});
const testCasesIndexed = validTestCases[resourceRecordType];
it.each(Object.keys(testCasesIndexed) as IValidMockKeyType[])(
'Should match expected - %s',
key => {
const input = validMock[key];
const expected = testCasesIndexed[key];
const { records } = testImportZoneFile(input);
expect(records).toEqual(expected);
}
);
}
);
describe('Warnings when there are ignored records in zone file', () => {
it('Should return no warnings when there are not SOA and/or NS without name', () => {
const input = validMock['nsExampleWithNamesAndTopLevelTTL'];
const { warnings } = testImportZoneFile(input);
expect(warnings).toEqual([]);
});
it('Should return warning when SOA is ignored', () => {
const input = validMock['soaIsIgnored2'];
const { warnings } = testImportZoneFile(input);
expect(warnings).toEqual([WarningKeyKind.SoaIgnored]);
});
it('Should return warning when NS is ignored', () => {
const input = validMock['nsExampleWithAtAndTopLevelTTL'];
const { warnings } = testImportZoneFile(input);
expect(warnings).toEqual([
WarningKeyKind.NsIgnored,
WarningKeyKind.NsIgnored,
]);
});
it('Should return warnings when SOA and NS are ignored', () => {
const input = validMock['soaIsIgnored1'];
const { warnings } = testImportZoneFile(input);
expect(warnings).toEqual([
WarningKeyKind.SoaIgnored,
WarningKeyKind.NsIgnored,
]);
});
});
describe('Edge cases', () => {
const edgeTestCases: MockWithoutTypeForKey<keyof typeof edgeMock> = {
atSymbol: [
{
name: 'www.igloo.com.',
ttl: 300,
type: 'CNAME',
endpoints: ['igloo.com.'],
},
],
atSymbolEscaped: [
{
name: 'www.igloo.com.',
ttl: 300,
type: 'CNAME',
endpoints: ['@'],
},
],
};
it.each(Object.keys(edgeTestCases) as Array<keyof typeof edgeTestCases>)(
'Should handle "%s"',
key => {
const input = edgeMock[key];
const expected = edgeTestCases[key];
const edgeResults = testImportZoneFile(input);
expect(edgeResults.records).toEqual(expected);
}
);
});
describe('Complex (valid)', () => {
const complexTestCases: MockWithoutTypeForKey<IComplexMockKeyType> = {
/** @note the ttls here are defaulted to 300 */
complicated1: [
{
name: 'server.igloo.com.',
endpoints: ['172.30.79.10'],
ttl: 300,
type: 'A',
},
{
name: 'old-slow-box.igloo.com.',
endpoints: ['172.30.79.11'],
ttl: 300,
type: 'A',
},
{
name: 'sysadmins-box.igloo.com.',
endpoints: ['172.30.79.12'],
ttl: 300,
type: 'A',
},
{
name: 'new-fast-box.igloo.com.',
endpoints: ['172.30.79.13'],
ttl: 300,
type: 'A',
},
{
name: '_foobar._tcp.igloo.com.',
endpoints: [
'0 1 9 old-slow-box.igloo.com.',
'0 3 9 new-fast-box.igloo.com.',
'1 0 9 sysadmins-box.igloo.com.',
'1 0 9 server.igloo.com.',
],
ttl: 300,
type: 'SRV',
},
{
name: '*._tcp.igloo.com.',
endpoints: ['0 0 0 .'],
ttl: 300,
type: 'SRV',
},
{
name: '*._udp.igloo.com.',
endpoints: ['0 0 0 .'],
ttl: 300,
type: 'SRV',
},
],
complicated2: [
{
name: 'neither.igloo.com.',
endpoints: ['1.2.3.4'],
type: 'A',
ttl: 604800,
},
{
name: 'ns.igloo.com.',
endpoints: ['2.3.4.5'],
type: 'A',
ttl: 100,
},
{
name: 'mail.igloo.com.',
endpoints: ['10.0.0.2'],
type: 'A',
ttl: 86400,
},
{
name: 'mail2.igloo.com.',
endpoints: ['10.0.0.3'],
type: 'A',
ttl: 86400,
},
{
name: 'www.igloo.com.',
endpoints: ['10.0.0.1'],
type: 'A',
ttl: 86400,
},
{
name: '\\097.igloo.com.',
endpoints: ['1.2.3.4'],
type: 'A',
ttl: 60,
},
{
name: '\\304.igloo.com.',
endpoints: ['1.2.3.4'],
type: 'A',
ttl: 60,
},
{
name: '©.igloo.com.',
endpoints: ['1.2.3.4'],
type: 'A',
ttl: 60,
},
{
name: '¿.igloo.com.',
endpoints: ['1.2.3.4'],
type: 'A',
ttl: 60,
},
{
name: 'www.igloo.com.',
endpoints: ['\\008\\99.igloo.com.'],
type: 'CNAME',
ttl: 60,
},
{
endpoints: ['igloo.com.'],
name: 'root.igloo.com.',
ttl: 86400,
type: 'CNAME',
},
{
name: 'web.igloo.com.',
endpoints: ['www'],
type: 'CNAME',
ttl: 86400,
},
{
name: 'www.igloo.com.',
endpoints: ['"\\101\\"'],
type: 'TXT',
ttl: 60,
},
{
name: 'test.igloo.com.',
endpoints: ['"v=spf1" "mx:gcloud-node.com." "-all"'],
type: 'SPF',
ttl: 604800,
},
{
name: 'test1.igloo.com.',
endpoints: [
'"v=spf2" "mx:gcloud-node.com." "-all"',
'"v=spf3" "mx:gcloud-node.com." "-all "',
'"v=spf4" "mx:gcloud-node.com." "-all"',
],
type: 'SPF',
ttl: 604800,
},
],
complicated3: [
{
name: 'igloo.com.',
endpoints: ['192.0.2.1'],
type: 'A',
ttl: 3600,
},
{
name: 'ns.igloo.com.',
endpoints: ['192.0.2.2'],
type: 'A',
ttl: 3600,
},
{
name: 'mail.igloo.com.',
endpoints: ['192.0.2.3'],
type: 'A',
ttl: 3600,
},
{
name: 'mail2.igloo.com.',
endpoints: ['192.0.2.4'],
type: 'A',
ttl: 3600,
},
{
name: 'mail3.igloo.com.',
endpoints: ['192.0.2.5'],
type: 'A',
ttl: 3600,
},
{
name: 'igloo.com.',
endpoints: ['2001:db8:10::1'],
type: 'AAAA',
ttl: 3600,
},
{
name: 'ns.igloo.com.',
endpoints: ['2001:db8:10::2'],
type: 'AAAA',
ttl: 3600,
},
{
name: 'www.igloo.com.',
endpoints: ['igloo.com.'],
type: 'CNAME',
ttl: 3600,
},
{
name: 'wwwtest.igloo.com.',
endpoints: ['www'],
type: 'CNAME',
ttl: 3600,
},
{
name: 'igloo.com.',
endpoints: ['10 mail.igloo.com.', '20 mail2.igloo.com.', '50 mail3'],
type: 'MX',
ttl: 3600,
},
],
complicated4: [
{
name: 'mail.igloo.com.',
endpoints: ['127.0.0.1', '1.2.3.4'],
type: 'A',
ttl: 1814400,
},
{
name: 'mail.igloo.com.',
endpoints: ['2001:db8::1'],
type: 'AAAA',
ttl: 1814400,
},
{
name: 'mail1.igloo.com.',
endpoints: ['mail'],
type: 'CNAME',
ttl: 1814400,
},
{
name: 'mail2.igloo.com.',
endpoints: ['mail'],
type: 'CNAME',
ttl: 1814400,
},
{
name: 'igloo.com.',
endpoints: ['0 mail1', '10 mail2'],
type: 'MX',
ttl: 1814400,
},
],
};
/** @note !!! the order is changed (from the consumer) */
it('Should handle - invalid nesting', () => {
const { records } = testImportZoneFile(
invalidClientMock.parsingErrorInvalidNesting
);
const expected = [
{
name: 'www.igloo.com.',
endpoints: ['5.6.7.8'],
type: 'A',
ttl: 42,
},
{
name: 'notme.igloo.com.',
endpoints: ['1.2.3.4'],
type: 'NS',
ttl: 42,
},
{
name: 'www.igloo.com.',
endpoints: ['9.10.11.12'],
type: 'NS',
ttl: 42,
},
];
expect(records).toEqual(expect.arrayContaining(expected));
});
it.each(Object.keys(complexTestCases) as IComplexMockKeyType[])(
'%s',
key => {
const expected = complexTestCases[key];
const input = complexMock[key];
const { records } = testImportZoneFile(input);
// expect(records).toEqual(expected);
expect(records).toEqual(expect.arrayContaining(expected!));
}
);
});
describe('Client side (invalid)', () => {
it('Should handle - unsupported type (+ handle line number)', () => {
const { error } = testImportZoneFile(invalidClientMock.unsupportedType);
expect(error!.message).toEqual(
getErrorText({
errorType: ErrorKeyKind.BadDirective,
} as any)
);
expect(error!.parsingLineContent).toEqual(
'www 42s IN DNAME www.otherzone.com.'
);
expect(error!.parsingLineNumber).toEqual(2);
});
it.each`
errorType | mock | case
${ErrorKeyKind.EmptyInput} | ${invalidClientMock.empty} | ${'empty'}
${ErrorKeyKind.BadDirective} | ${invalidClientMock.badDirectiveInclude} | ${'bad directive (import)'}
${ErrorKeyKind.BadDirective} | ${invalidClientMock.badDirectiveUnknown} | ${'bad directive (unknown)'}
${ErrorKeyKind.BadDirective} | ${invalidClientMock.incompleteRecords} | ${'incomplete record'}
${ErrorKeyKind.NoRecords} | ${invalidClientMock.onlyComment} | ${'no records'}
${ErrorKeyKind.WrongNameArgsLength} | ${invalidClientMock.tooManyNameArgs} | ${'too many args for name/ttl'}
${ErrorKeyKind.Duplicated} | ${invalidClientMock.duplicateRecord} | ${'duplicated'}
${ErrorKeyKind.EmptyInput} | ${invalidClientMock.empty} | ${'empty'}
${ErrorKeyKind.TtlInvalidFormat} | ${invalidClientMock.badTtl} | ${'invalid ttl'}
`(
'Should handle "$case"',
({ errorType, mock }: { errorType: ErrorKeyKind; mock: string }) => {
const zone = testImportZoneFile(mock);
const { error } = zone;
expect(error!.message).toEqual(getErrorText({ errorType } as any));
}
);
});
describe('With wrong arg lengths (invalid)', () => {
const argLengths = {
txtWithNotEnoughArgs: {
expectedArgCount: RecordMinLengthKind.TXT,
actualArgCount: 0,
},
txtWithNotEnoughArgsOrAnything: {
expectedArgCount: RecordMinLengthKind.TXT,
actualArgCount: 0,
},
txtWithTooManyEntries: {
expectedArgCount: RecordMinLengthKind.TXT,
actualArgCount: RecordMinLengthKind.TXT + 1,
},
txtWithTooManyEntriesNoTtl: {
expectedArgCount: RecordMinLengthKind.TXT,
actualArgCount: RecordMinLengthKind.TXT + 1,
},
txtWithTooManyEntriesNoNameOrTtl: {
expectedArgCount: RecordMinLengthKind.TXT,
actualArgCount: RecordMinLengthKind.TXT + 1,
},
ptrWithTooManyEntries: {
expectedArgCount: RecordMinLengthKind.PTR,
actualArgCount: RecordMinLengthKind.PTR + 1,
},
ptrWithNotEnoughArgs1: {
expectedArgCount: RecordMinLengthKind.PTR,
actualArgCount: RecordMinLengthKind.PTR - 1,
},
aaaaWithTooManyEntries: {
expectedArgCount: RecordMinLengthKind.AAAA,
actualArgCount: RecordMinLengthKind.AAAA + 1,
},
aaaaWithNotEnoughArgs1: {
expectedArgCount: RecordMinLengthKind.AAAA,
actualArgCount: RecordMinLengthKind.AAAA - 1,
},
aWithTooManyEntries: {
expectedArgCount: RecordMinLengthKind.A,
actualArgCount: RecordMinLengthKind.A + 1,
},
aWithNotEnoughArgs1: {
expectedArgCount: RecordMinLengthKind.A,
actualArgCount: RecordMinLengthKind.A - 1,
},
spfWithNotEnoughArgs2: {
expectedArgCount: RecordMinLengthKind.SPF,
actualArgCount: RecordMinLengthKind.SPF - 1,
},
spfWithNotEnoughArgs3: {
expectedArgCount: RecordMinLengthKind.SPF,
actualArgCount: 0,
},
caaWithTooManyArgs: {
expectedArgCount: RecordMinLengthKind.CAA,
actualArgCount: RecordMinLengthKind.CAA + 1,
},
caaWithNotEnoughArgs1: {
expectedArgCount: RecordMinLengthKind.CAA,
actualArgCount: RecordMinLengthKind.CAA - 1,
},
caaWithNotEnoughArgs2: {
expectedArgCount: RecordMinLengthKind.CAA,
actualArgCount: RecordMinLengthKind.CAA - 2,
},
caaWithNotEnoughArgs3: {
expectedArgCount: RecordMinLengthKind.CAA,
actualArgCount: 0,
},
naptrWithTooManyArgs: {
expectedArgCount: RecordMinLengthKind.NAPTR,
actualArgCount: RecordMinLengthKind.NAPTR + 1,
},
naptrWithNotEnoughArgs1: {
expectedArgCount: RecordMinLengthKind.NAPTR,
actualArgCount: RecordMinLengthKind.NAPTR - 1,
},
naptrWithNotEnoughArgs2: {
expectedArgCount: RecordMinLengthKind.NAPTR,
actualArgCount: RecordMinLengthKind.NAPTR - 2,
},
naptrWithNotEnoughArgs3: {
expectedArgCount: RecordMinLengthKind.NAPTR,
actualArgCount: RecordMinLengthKind.NAPTR - 3,
},
naptrWithNotEnoughArgs4: {
expectedArgCount: RecordMinLengthKind.NAPTR,
actualArgCount: RecordMinLengthKind.NAPTR - 4,
},
naptrWithNotEnoughArgs5: {
expectedArgCount: RecordMinLengthKind.NAPTR,
actualArgCount: RecordMinLengthKind.NAPTR - 5,
},
naptrWithNotEnoughArgs6: {
expectedArgCount: RecordMinLengthKind.NAPTR,
actualArgCount: 0,
},
naptrWithNotEnoughArgs7: {
expectedArgCount: RecordMinLengthKind.NAPTR,
actualArgCount: 0,
},
srvWithTooManyArgs: {
expectedArgCount: RecordMinLengthKind.SRV,
actualArgCount: RecordMinLengthKind.SRV + 1,
},
srvWithNotEnoughArgs1: {
expectedArgCount: RecordMinLengthKind.SRV,
actualArgCount: RecordMinLengthKind.SRV - 1,
},
srvWithNotEnoughArgs2: {
expectedArgCount: RecordMinLengthKind.SRV,
actualArgCount: RecordMinLengthKind.SRV - 2,
},
srvWithNotEnoughArgs3: {
expectedArgCount: RecordMinLengthKind.SRV,
actualArgCount: RecordMinLengthKind.SRV - 3,
},
srvWithNotEnoughArgs4: {
expectedArgCount: RecordMinLengthKind.SRV,
actualArgCount: RecordMinLengthKind.SRV - 4,
},
srvWithNotEnoughArgs5: {
expectedArgCount: RecordMinLengthKind.SRV,
actualArgCount: 0,
},
nsWithTooManyArgs: {
expectedArgCount: RecordMinLengthKind.NS,
actualArgCount: RecordMinLengthKind.NS + 1,
},
nsWithNotEnoughArgs1: {
expectedArgCount: RecordMinLengthKind.NS,
actualArgCount: 0,
},
nsWithNotEnoughArgs2: {
expectedArgCount: RecordMinLengthKind.NS,
actualArgCount: 0,
},
nsWithNotEnoughArgs3: {
expectedArgCount: RecordMinLengthKind.NS,
actualArgCount: 0,
},
nsWithNotEnoughArgs4: {
expectedArgCount: RecordMinLengthKind.NS,
actualArgCount: 0,
},
mxWithTooManyArgs: {
expectedArgCount: RecordMinLengthKind.MX,
actualArgCount: RecordMinLengthKind.MX + 1,
},
mxWithNotEnoughArgs1: {
expectedArgCount: RecordMinLengthKind.MX,
actualArgCount: RecordMinLengthKind.MX - 1,
},
mxWithNotEnoughArgs2: {
expectedArgCount: RecordMinLengthKind.MX,
actualArgCount: 0,
},
cnameWithTooManyArgs: {
expectedArgCount: RecordMinLengthKind.CNAME,
actualArgCount: RecordMinLengthKind.CNAME + 1,
},
cnameWithNotEnoughArgs1: {
expectedArgCount: RecordMinLengthKind.CNAME,
actualArgCount: 0,
},
cnameWithNotEnoughArgs2: {
expectedArgCount: RecordMinLengthKind.CNAME,
actualArgCount: 0,
},
};
it.each(Object.keys(argLengths) as Array<keyof typeof argLengths>)(
'Should throw - `%s`',
key => {
const input = invalidClientMock[key];
const error: ParserInvalidArgumentsError &
ParsingError = testImportZoneFile(input).error as any;
const { expectedArgCount, actualArgCount } = argLengths[key];
expect(error).toMatchSnapshot();
/**
* @note this is not here unless i18n was added
* @example /Too many args\! Expected (\d) for type [a-z]\. Received (\d)/gmi)
*
* @example
* const matches = match(/(\d+)/gim, error!.message).map(Number);
* expect(matches).toHaveLength(2)
* const [matchedExpected, matchedActual] = matches;
*/
const { expected: matchedExpected, actual: matchedActual } = JSON.parse(
error?.message
);
expect(matchedExpected).toEqual(expectedArgCount);
expect(matchedActual).toEqual(actualArgCount);
}
);
});
describe('API side (invalid)', () => {
let spy: jest.SpyInstance;
beforeEach(() => {
spy = spyOnCreateApi().mockImplementation(() =>
Promise.reject(new Error('Invalid!'))
);
});
// these will have other types as well
const invalidApiTestCases: {
[K in IInvalidApiMockKeyType]: { message: string };
} = {
duplicateDistanceRecord: {
message: 'Invalid!',
},
labelTooLong: {
message: 'Invalid!',
},
nameTooLong: {
message: 'Invalid!',
},
negativeTtl: {
message: 'Invalid!',
},
escapedCharacters: {
message: 'Invalid!',
},
cnameLoop1: {
message: 'Invalid!',
},
cnameLoop2: {
message: 'Invalid!',
},
cnameLoop3: {
message: 'Invalid!',
},
cnameForExistingRecord: {
message: 'Invalid!',
},
mxExample1: {
message: 'Invalid!',
},
invalidIpForAAAA: {
message: 'Invalid!',
},
invalidIpv6ForA: {
message: 'Invalid!',
},
invalidIpv4ForAAAA: {
message: 'Invalid!',
},
invalidIpForA: {
message: 'Invalid!',
},
txtExampleInvalidSubdomain: {
message: 'Invalid!',
},
caaExampleBadParams: {
message: 'Invalid!',
},
caaExampleNotEmailForIoDef: {
message: 'Invalid!',
},
naptrExampleInvalid: {
message: 'Invalid!',
},
};
it.each(
Object.keys(invalidApiTestCases) as Array<
keyof typeof invalidApiTestCases
>
)('Should throw - `%s`', async key => {
const input = invalidApiMock[key];
const expected = invalidApiTestCases[key];
expect.assertions(2);
try {
await testImportZoneFileAndCallApi(input);
} catch (error) {
expect(error!.message).toEqual(expected.message);
}
expect(spy).toHaveBeenCalled();
});
});
});