Add DuckDNS as a free DNS provider option (#124)

DuckDNS's API is smaller than the other providers' (no list/read API,
no arbitrary sub-records, one A/AAAA + one TXT record per domain), so
domains are entered by the operator instead of auto-discovered, and
getRecords reads from public DNS since there's nothing else to query.
Documented as a free option in the README and DNS provider docs.
This commit is contained in:
2026-07-13 23:41:09 -04:00
committed by GitHub
parent 5139fbb79a
commit d5df5baca1
9 changed files with 229 additions and 5 deletions
@@ -153,6 +153,54 @@ describe('DNS Provider Contract Compliance', () => {
validateTypeChecking(instance);
});
});
describe('DuckDNS Provider', () => {
const DuckDns = require('../../models/dns_provider/duckdns');
test('should meet DNS provider contract', () => {
const mockCredentials = {token: 'mock-token', domains: 'mockhost'};
const instance = validateDnsProviderContract(DuckDns, mockCredentials);
assert.ok(instance, 'DuckDNS provider should be instantiated');
});
test('should have correct _keyMap structure', () => {
assert.ok(DuckDns._keyMap.token, 'Should require token');
assert.strictEqual(DuckDns._keyMap.token.type, 'string');
assert.strictEqual(DuckDns._keyMap.token.isRequired, true);
assert.strictEqual(DuckDns._keyMap.token.isPrivate, true);
assert.ok(DuckDns._keyMap.domains, 'Should require domains');
assert.strictEqual(DuckDns._keyMap.domains.isRequired, true);
});
test('should have valid method signatures', () => {
const instance = new DuckDns({token: 'mock-token', domains: 'mockhost'});
validateMethodSignatures(instance);
});
test('should validate key mapping', () => {
const instance = new DuckDns({token: 'mock-token', domains: 'mockhost'});
validateKeyMapping(instance);
});
test('should validate type checking', () => {
const instance = new DuckDns({token: 'mock-token', domains: 'mockhost'});
validateTypeChecking(instance);
});
test('rejects non A/AAAA/TXT record creation with a clear error', async () => {
const instance = new DuckDns({token: 'mock-token', domains: 'mockhost'});
await assert.rejects(
() => instance.createRecord({domain: 'mockhost.duckdns.org'}, {type: 'CNAME', data: 'example.com'}),
/DuckDNS only supports A, AAAA and TXT records/
);
});
test('__label strips the .duckdns.org suffix', () => {
const instance = new DuckDns({token: 'mock-token', domains: 'mockhost'});
assert.strictEqual(instance.__label({domain: 'mockhost.duckdns.org'}), 'mockhost');
});
});
});
/**