UNPKG

hostparty

Version:

Programmatic and CLI editing for OS hosts file

1,224 lines (1,071 loc) 41.9 kB
/** * logging tests */ (()=>{ 'use strict'; var chai = require('chai'), hostparty = require('../lib/party'), utils = require('../lib/utils'), constants = require('../lib/constants'), hooks = require('./tests-setup'), expect = chai.expect; /** * hosts file tests */ describe('Hosts file CRUD operations:', ()=>{ /** * sets options before each test */ beforeEach(() => { hostparty.setup({ // set the path manually. overrides the host mapping. path: hooks.path, // disable backups during tests autoBackup: false }); }); /** * log types * * get types of log that are filterable */ it('Should return all entries in the test hosts file as a dictionary object.', (done)=>{ hostparty .list() .then((hosts)=>{ expect(hosts).to.be.an('object'); expect(hosts).to.have.property('::1'); expect(hosts).to.have.property('1.2.3.4'); expect(hosts).to.have.property('1.2.3.5'); expect(hosts).to.have.property('5.5.5.5'); expect(hosts).to.have.property('8.8.4.4'); expect(hosts).to.have.property('8.8.8.8'); expect(hosts).to.have.property('9.8.7.6'); expect(hosts).to.have.property('10.5.6.7'); expect(hosts).to.have.property('10.20.30.40'); expect(hosts).to.have.property('45.6.7.8'); expect(hosts).to.have.property('67.89.67.89'); expect(hosts).to.have.property('172.16.0.1'); done(); }) .catch(done); }); it('Should merge duplicate IPs from multiple lines.', (done)=>{ hostparty .list() .then((hosts)=>{ // 67.89.67.89 appears on two lines with dogs.com and cats.com expect(hosts['67.89.67.89']).to.include('dogs.com'); expect(hosts['67.89.67.89']).to.include('cats.com'); expect(hosts['67.89.67.89']).to.have.length(2); done(); }) .catch(done); }); it('Should ignore comment lines.', (done)=>{ hostparty .list() .then((hosts)=>{ // Comments should not appear as IPs const ips = Object.keys(hosts); const hasCommentAsIP = ips.some(ip => ip.startsWith('#')); expect(hasCommentAsIP).to.be.false; done(); }) .catch(done); }); it('Should handle tabs and multiple spaces as delimiters.', (done)=>{ hostparty .list() .then((hosts)=>{ // 1.2.3.4 uses tabs, 5.5.5.5 uses multiple spaces expect(hosts['1.2.3.4']).to.include('caps.lol'); expect(hosts['5.5.5.5']).to.include('five.five'); done(); }) .catch(done); }); it('Should merge hostnames when adding to existing IP.', (done)=>{ const existingIP = '8.8.8.8'; // already has dns.google.com, dns.google.de const newHost = 'new-host.google.com'; hostparty .add(existingIP, [newHost]) .then(() => { return hostparty.list(); }) .then((hosts) => { // Should have original hosts plus new one expect(hosts[existingIP]).to.include('dns.google.com'); expect(hosts[existingIP]).to.include('dns.google.de'); expect(hosts[existingIP]).to.include(newHost); expect(hosts[existingIP].length).to.be.at.least(3); done(); }) .catch(done); }); /** * log types * * get types of log that are filterable */ it('Add a new entry for IP 1.1.2.2 with 2 host names.', (done)=>{ let ip = '1.1.2.3'; hostparty .add(ip, ['dogs.foo', 'cats.things']) .then(()=>{ hostparty .list() .then((hosts)=>{ expect(hosts).to.be.an('object'); expect(hosts).to.have.property(ip); expect(hosts[ip]).to.have.length(2); expect(hosts[ip][0]).to.equal('cats.things'); expect(hosts[ip][1]).to.equal('dogs.foo'); done(); }) .catch(done); }) .catch(done); }); /** * log types * * get types of log that are filterable */ it('Remove hostname entry from 1.1.2.2.', (done)=>{ let ip = '1.1.2.3', host = 'cats.things'; hostparty .removeHost(host) .then(()=>{ hostparty .list() .then((hosts)=>{ expect(hosts).to.be.an('object'); expect(hosts).to.have.property(ip); expect(hosts[ip]).to.have.length(1); expect(hosts[ip][0]).to.equal('dogs.foo'); done(); }) .catch(done); }) .catch(done); }); /** * log types * * get types of log that are filterable */ it('Remove another hostname entry from 1.1.2.2, removing the IP entirely', (done)=>{ let ip = '1.1.2.3', host = 'dogs.foo'; hostparty .removeHost(host) .then(()=>{ hostparty .list() .then((hosts)=>{ expect(hosts).to.be.an('object'); expect(hosts).to.not.have.property(ip); done(); }) .catch(done); }) .catch(done); }); /** * log types * * get types of log that are filterable */ it('Add a new entry for IPv6 2001:0db8:85a3:0000:0000:8a2e:0370:7334 with 2 host names.', (done)=>{ let ipv6 = '2001:0db8:85a3:0000:0000:8a2e:0370:7334'; hostparty .add(ipv6, ['ipv6.test.com']) .then(()=>{ hostparty .list() .then((hosts)=>{ expect(hosts).to.be.an('object'); expect(hosts).to.have.property(ipv6); expect(hosts[ipv6]).to.have.length(1); expect(hosts[ipv6][0]).to.equal('ipv6.test.com'); done(); }) .catch(done); }) .catch((e)=>{ done(e); }); }); /** * log types * * get types of log that are filterable */ it('Add bad IP and fail.', (done)=>{ let badIp = 'x1.2.d3.a4'; hostparty .add(badIp, ['irrelevant.com']) .then(()=>{ done(new Error('Failed to trap error')); }) .catch(()=>{ hostparty .list() .then((hosts)=>{ expect(hosts).to.be.an('object'); expect(hosts).to.not.have.property(badIp); done(); }) .catch(done); }); }); /** * log types * * get types of log that are filterable */ it('Removes a host name [foo.net].', (done)=>{ let removedHost = 'foo.net'; hostparty .removeHost(removedHost) .then(()=>{ hostparty .list() .then((hosts)=>{ expect(hosts).to.be.an('object'); expect(hosts['10.5.6.7']).to.not.include(removedHost); done(); }) .catch(done); }) .catch(done); }); /** * log types * * get types of log that are filterable */ it('Removes an IP address [8.8.4.4] and all mapped hosts.', (done)=>{ let ip = '8.8.4.4'; hostparty .removeIP(ip) .then(()=>{ hostparty .list() .then((hosts)=>{ expect(hosts).to.be.an('object'); expect(hosts).to.not.have.property(ip); done(); }) .catch(done); }) .catch((e)=>{ done(new Error(e)); }); }); /** * remove protected * * tries to delete a known protected ip. expected to fail. */ it('Attempt to remove a protected IP address [::1] and be rejected.', (done)=>{ hostparty .removeIP('::1') .then(()=>{ done(new Error('Failed to trap error')); }) .catch(()=>{ done(); }); }); /** * remove protected * * tries to delete a known protected ip. expected to fail. */ // it('Attempt to disable an IP address [10.20.30.40].', function (done) { // // let disabledIP = '10.20.30.40'; // // hostparty // .disable(disabledIP) // .then(function() { // // hostparty // .list() // .then(function(hosts) { // expect(hosts).to.be.an('object'); // expect(hosts).to.not.have.property(disabledIP); // done(); // }) // .catch(done); // }) // .catch(function(e) { // done(new Error(e)); // }); // }); /** * remove protected * * tries to delete a known protected ip. expected to fail. */ // it('Attempt to enable a previously disabled IP address [10.20.30.40].', function (done) { // // let enabledIP = '10.20.30.40'; // // hostparty // .enable(enabledIP) // .then(function() { // // hostparty // .list() // .then(function(hosts) { // expect(hosts).to.be.an('object'); // expect(hosts).to.have.property(enabledIP); // done(); // }) // .catch(done); // }) // .catch(function(e) { // done(new Error(e)); // }); // }); }); /** * smart argument detection tests */ describe('Smart argument detection:', () => { it('Should detect when hostname and IP are swapped', (done) => { const result = utils.detectArgumentSwap('example.com', '192.168.1.100'); expect(result.shouldSwap).to.be.true; expect(result.suggestion).to.include('192.168.1.100 example.com'); expect(result.correctedIP).to.equal('192.168.1.100'); expect(result.correctedHost).to.equal('example.com'); done(); }); it('Should not suggest swap when IP is first', (done) => { const result = utils.detectArgumentSwap('192.168.1.100', 'example.com'); expect(result.shouldSwap).to.be.false; done(); }); it('Should not suggest swap with invalid IP', (done) => { const result = utils.detectArgumentSwap('example.com', 'not-an-ip'); expect(result.shouldSwap).to.be.false; done(); }); it('Should not suggest swap with invalid hostname', (done) => { const result = utils.detectArgumentSwap('invalid_host!', '192.168.1.100'); expect(result.shouldSwap).to.be.false; done(); }); }); /** * validation function tests */ describe('Validation functions:', () => { it('Should validate IPv4 addresses correctly', (done) => { expect(utils.validateIP('192.168.1.1')).to.be.true; expect(utils.validateIP('127.0.0.1')).to.be.true; expect(utils.validateIP('0.0.0.0')).to.be.true; expect(utils.validateIP('255.255.255.255')).to.be.true; done(); }); it('Should validate IPv6 addresses correctly', (done) => { expect(utils.validateIP('2001:0db8:85a3:0000:0000:8a2e:0370:7334')).to.be.true; expect(utils.validateIP('2001:db8::1')).to.be.true; expect(utils.validateIP('fe80::1')).to.be.true; // note: ::1 and scope identifiers like %lo0 are not supported by is-ip library done(); }); it('Should reject invalid IP addresses', (done) => { expect(utils.validateIP('256.1.1.1')).to.be.false; expect(utils.validateIP('192.168.1')).to.be.false; expect(utils.validateIP('not-an-ip')).to.be.false; expect(utils.validateIP('192.168.1.1.1')).to.be.false; expect(utils.validateIP('')).to.be.false; done(); }); it('Should validate hostnames correctly', (done) => { expect(utils.validateHost('example.com')).to.be.true; expect(utils.validateHost('sub.example.com')).to.be.true; expect(utils.validateHost('localhost')).to.be.true; expect(utils.validateHost('test-host.org')).to.be.true; expect(utils.validateHost('123.org')).to.be.true; done(); }); it('Should reject invalid hostnames', (done) => { expect(utils.validateHost('')).to.be.false; expect(utils.validateHost('invalid_host!')).to.be.false; expect(utils.validateHost('.example.com')).to.be.false; expect(utils.validateHost('example..com')).to.be.false; expect(utils.validateHost('example.com.')).to.be.false; done(); }); }); /** * setup and configuration tests */ describe('Setup and configuration:', () => { const fs = require('fs'); const origPath = require('path').resolve('./tests/etc/hosts.test.orig'); beforeEach(() => { // Reset file to original state before each test fs.writeFileSync(hooks.path, fs.readFileSync(origPath)); hostparty.setup({ path: hooks.path, force: false, autoBackup: false }); }); it('Should return hostparty instance for chaining', (done) => { const result = hostparty.setup({ force: false, autoBackup: false }); expect(result).to.equal(hostparty); done(); }); it('Should allow chained method calls', (done) => { hostparty .setup({ path: hooks.path, force: false }) .list() .then((hosts) => { expect(hosts).to.be.an('object'); done(); }) .catch(done); }); it('Should allow force removal of protected IP', (done) => { // First verify ::1 exists hostparty .list() .then((hosts) => { expect(hosts).to.have.property('::1'); // Now force remove it return hostparty .setup({ path: hooks.path, force: true }) .removeIP('::1'); }) .then(() => { return hostparty.list(); }) .then((hosts) => { expect(hosts).to.not.have.property('::1'); done(); }) .catch(done); }); }); /** * error handling and edge case tests */ describe('Error handling and edge cases:', () => { const fs = require('fs'); const origPath = require('path').resolve('./tests/etc/hosts.test.orig'); beforeEach(() => { // Reset file to original state before each test fs.writeFileSync(hooks.path, fs.readFileSync(origPath)); hostparty.setup({ path: hooks.path, force: false, autoBackup: false }); }); it('Should reject empty IP when adding', (done) => { hostparty .add('', ['example.com']) .then(() => { done(new Error('Should have rejected empty IP')); }) .catch(() => { done(); }); }); it('Should handle empty hostnames by filtering them out', (done) => { const testIP = '1.2.3.110'; hostparty .add(testIP, ['', 'valid-host.com', '']) .then(() => { return hostparty.list(); }) .then((hosts) => { expect(hosts[testIP]).to.have.length(1); expect(hosts[testIP]).to.include('valid-host.com'); done(); }) .catch(done); }); it('Should reject when both IP and hostnames are empty', (done) => { hostparty .add('', ['', '', '']) .then(() => { done(new Error('Should have rejected empty IP and hostnames')); }) .catch(() => { done(); }); }); it('Should handle multiple hostnames for same IP', (done) => { const testIP = '1.2.3.100'; hostparty .add(testIP, ['host1.com', 'host2.com', 'host3.com']) .then(() => { return hostparty.list(); }) .then((hosts) => { expect(hosts[testIP]).to.have.length(3); expect(hosts[testIP]).to.include('host1.com'); expect(hosts[testIP]).to.include('host2.com'); expect(hosts[testIP]).to.include('host3.com'); done(); }) .catch(done); }); it('Should handle duplicate hostnames gracefully', (done) => { const testIP = '1.2.3.101'; hostparty .add(testIP, ['duplicate.com', 'duplicate.com', 'unique.com']) .then(() => { return hostparty.list(); }) .then((hosts) => { expect(hosts[testIP]).to.have.length(2); expect(hosts[testIP]).to.include('duplicate.com'); expect(hosts[testIP]).to.include('unique.com'); done(); }) .catch(done); }); it('Should reject removing non-existent IP', (done) => { hostparty .removeIP('99.99.99.99') .then(() => { done(new Error('Should have rejected removing non-existent IP')); }) .catch(() => { done(); }); }); it('Should handle purging non-existent hostname gracefully', (done) => { hostparty .removeHost('non-existent-host.com') .then(() => { // should succeed even if hostname doesn't exist done(); }) .catch(done); }); it('Should handle case-insensitive hostname purging', (done) => { const testIP = '1.2.3.102'; const testHost = 'CaseSensitive.com'; hostparty .add(testIP, [testHost]) .then(() => { return hostparty.removeHost('casesensitive.com'); // different case }) .then(() => { return hostparty.list(); }) .then((hosts) => { expect(hosts).to.not.have.property(testIP); done(); }) .catch(done); }); it('Should protect against removing multiple protected IPs', (done) => { hostparty .removeIP(['::1', 'fe80::1%lo0']) .then(() => { done(new Error('Should have rejected removing protected IPs')); }) .catch(() => { done(); }); }); it('Should protect against removing protected hostnames', (done) => { hostparty .removeHost('localhost') .then(() => { done(new Error('Should have rejected removing protected hostname')); }) .catch((err) => { expect(err).to.include('protected hostname'); done(); }); }); it('Should protect against removing 127.0.0.1', (done) => { hostparty .removeIP('127.0.0.1') .then(() => { done(new Error('Should have rejected removing 127.0.0.1')); }) .catch((err) => { expect(err).to.include('protected'); done(); }); }); it('Should handle IPv6 compressed notation', (done) => { const ipv6 = '2001:db8::1'; hostparty .add(ipv6, ['ipv6-compressed.test']) .then(() => { return hostparty.list(); }) .then((hosts) => { expect(hosts).to.have.property(ipv6); expect(hosts[ipv6]).to.include('ipv6-compressed.test'); done(); }) .catch(done); }); it('Should validate invalid hostname formats in add', (done) => { hostparty .add('192.168.1.50', ['invalid..hostname', 'valid-host.com']) .then(() => { done(new Error('Should have rejected invalid hostname')); }) .catch(() => { done(); }); }); }); /** * filtering and listing tests - uses fresh copy of test data */ describe('Filtering and listing:', () => { const fs = require('fs'); const fsPromises = require('fs').promises; const origPath = require('path').resolve('./tests/etc/hosts.test.orig'); beforeEach(async () => { // Reset file to original state before each test (async to ensure flush) const content = await fsPromises.readFile(origPath); await fsPromises.writeFile(hooks.path, content); hostparty.setup({ path: hooks.path, autoBackup: false }); }); it('Should filter hosts by hostname pattern', (done) => { hostparty .list('com') .then((hosts) => { expect(hosts).to.be.an('object'); const allHosts = Object.values(hosts).flat(); const hasComHosts = allHosts.some(host => host.includes('com')); expect(hasComHosts).to.be.true; done(); }) .catch(done); }); it('Should return empty result for non-matching filter', (done) => { hostparty .list('non-existent-pattern-xyz123') .then((hosts) => { expect(Object.keys(hosts)).to.have.length(0); done(); }) .catch(done); }); }); /** * constants integration tests */ describe('Constants integration:', () => { it('Should have all required constant categories', (done) => { expect(constants).to.have.property('PROTECTED_ENTRIES'); expect(constants).to.have.property('PLATFORMS'); expect(constants).to.have.property('PATHS'); expect(constants).to.have.property('ERROR_CODES'); expect(constants).to.have.property('USER_ROLES'); expect(constants).to.have.property('USER_RESPONSES'); expect(constants).to.have.property('MESSAGES'); expect(constants).to.have.property('REGEX'); expect(constants).to.have.property('DEFAULT_OPTIONS'); expect(constants).to.have.property('OUTPUT'); done(); }); it('Should have correct protected entries', (done) => { // Protected IPs expect(constants.PROTECTED_ENTRIES.IPS).to.include('127.0.0.1'); expect(constants.PROTECTED_ENTRIES.IPS).to.include('::1'); expect(constants.PROTECTED_ENTRIES.IPS).to.include('fe80::1%lo0'); expect(constants.PROTECTED_ENTRIES.IPS).to.include('255.255.255.255'); // Protected hostnames expect(constants.PROTECTED_ENTRIES.HOSTS).to.include('localhost'); expect(constants.PROTECTED_ENTRIES.HOSTS).to.include('broadcasthost'); done(); }); it('Should have all platform constants', (done) => { expect(constants.PLATFORMS.LINUX).to.equal('linux'); expect(constants.PLATFORMS.DARWIN).to.equal('darwin'); expect(constants.PLATFORMS.WIN32).to.equal('win32'); done(); }); }); /** * disable/enable tests */ describe('Disable and enable operations:', () => { const fs = require('fs'); const origPath = require('path').resolve('./tests/etc/hosts.test.orig'); beforeEach(() => { fs.writeFileSync(hooks.path, fs.readFileSync(origPath)); hostparty.setup({ path: hooks.path, force: false, autoBackup: false }); }); it('Should disable an IP entry', (done) => { const testIP = '10.20.30.40'; hostparty .disable(testIP) .then(() => { return hostparty.list(); }) .then((hosts) => { expect(hosts).to.not.have.property(testIP); expect(hosts).to.have.property('# ' + testIP); done(); }) .catch(done); }); it('Should enable a previously disabled IP entry', (done) => { const testIP = '10.20.30.40'; hostparty .disable(testIP) .then(() => { return hostparty.enable(testIP); }) .then(() => { return hostparty.list(); }) .then((hosts) => { expect(hosts).to.have.property(testIP); expect(hosts).to.not.have.property('# ' + testIP); done(); }) .catch(done); }); it('Should reject disabling non-existent IP', (done) => { hostparty .disable('99.99.99.99') .then(() => { done(new Error('Should have rejected non-existent IP')); }) .catch(() => { done(); }); }); it('Should reject enabling non-disabled IP', (done) => { hostparty .enable('10.20.30.40') .then(() => { done(new Error('Should have rejected non-disabled IP')); }) .catch(() => { done(); }); }); }); /** * search-ip tests */ describe('Search by IP operations:', () => { const fs = require('fs'); const origPath = require('path').resolve('./tests/etc/hosts.test.orig'); beforeEach(() => { fs.writeFileSync(hooks.path, fs.readFileSync(origPath)); hostparty.setup({ path: hooks.path, autoBackup: false }); }); it('Should find hostnames for existing IP', (done) => { hostparty .searchByIP('8.8.8.8') .then((result) => { expect(result).to.not.be.null; expect(result.ip).to.equal('8.8.8.8'); expect(result.hostnames).to.be.an('array'); expect(result.hostnames.length).to.be.at.least(1); done(); }) .catch(done); }); it('Should return null for non-existent IP', (done) => { hostparty .searchByIP('99.99.99.99') .then((result) => { expect(result).to.be.null; done(); }) .catch(done); }); it('Should reject invalid IP format', (done) => { hostparty .searchByIP('not-an-ip') .then(() => { done(new Error('Should have rejected invalid IP')); }) .catch(() => { done(); }); }); }); /** * replace-ip tests */ describe('Replace IP operations:', () => { const fs = require('fs'); const origPath = require('path').resolve('./tests/etc/hosts.test.orig'); beforeEach(() => { fs.writeFileSync(hooks.path, fs.readFileSync(origPath)); hostparty.setup({ path: hooks.path, force: false, autoBackup: false }); }); it('Should move hostnames from one IP to another', (done) => { const fromIP = '10.20.30.40'; const toIP = '10.20.30.50'; hostparty .list() .then((hosts) => { expect(hosts).to.have.property(fromIP); return hostparty.replaceIP(fromIP, toIP); }) .then(() => { return hostparty.list(); }) .then((hosts) => { expect(hosts).to.not.have.property(fromIP); expect(hosts).to.have.property(toIP); done(); }) .catch(done); }); it('Should copy hostnames when --keep-source is used', (done) => { const fromIP = '10.20.30.40'; const toIP = '10.20.30.50'; hostparty .replaceIP(fromIP, toIP, true) // keepSource = true .then(() => { return hostparty.list(); }) .then((hosts) => { expect(hosts).to.have.property(fromIP); expect(hosts).to.have.property(toIP); done(); }) .catch(done); }); it('Should reject when source IP does not exist', (done) => { hostparty .replaceIP('99.99.99.99', '10.20.30.50') .then(() => { done(new Error('Should have rejected non-existent source IP')); }) .catch(() => { done(); }); }); it('Should reject when source and destination are the same', (done) => { hostparty .replaceIP('10.20.30.40', '10.20.30.40') .then(() => { done(new Error('Should have rejected same source and destination')); }) .catch(() => { done(); }); }); }); /** * dry-run tests */ describe('Dry-run mode:', () => { const fs = require('fs'); const origPath = require('path').resolve('./tests/etc/hosts.test.orig'); beforeEach(() => { fs.writeFileSync(hooks.path, fs.readFileSync(origPath)); }); it('Should return dry-run result without modifying file', (done) => { const testIP = '99.99.99.99'; const testHost = 'dryrun.test.com'; hostparty .setup({ path: hooks.path, dryRun: true, autoBackup: false }) .add(testIP, [testHost]) .then((result) => { expect(result).to.have.property('dryRun', true); expect(result).to.have.property('message'); expect(result).to.have.property('preview'); expect(result.preview).to.include(testIP); expect(result.preview).to.include(testHost); // Verify file was NOT modified return hostparty .setup({ path: hooks.path, dryRun: false, autoBackup: false }) .list(); }) .then((hosts) => { expect(hosts).to.not.have.property(testIP); done(); }) .catch(done); }); it('Should work with removeIP in dry-run mode', (done) => { hostparty .setup({ path: hooks.path, dryRun: true, autoBackup: false }) .removeIP('10.20.30.40') .then((result) => { expect(result).to.have.property('dryRun', true); expect(result.preview).to.not.include('10.20.30.40'); done(); }) .catch(done); }); }); /** * backup/restore tests */ describe('Backup and restore operations:', () => { const fs = require('fs'); const path = require('path'); const origPath = path.resolve('./tests/etc/hosts.test.orig'); beforeEach(() => { fs.writeFileSync(hooks.path, fs.readFileSync(origPath)); hostparty.setup({ path: hooks.path, autoBackup: false, dryRun: false }); }); it('Should create a backup file', (done) => { hostparty .createBackup() .then((backupPath) => { expect(backupPath).to.be.a('string'); expect(backupPath).to.include('hosts.backup.'); // Clean up return fs.promises.unlink(backupPath); }) .then(() => done()) .catch(done); }); it('Should list backup files', (done) => { hostparty .createBackup() .then(() => { return hostparty.listBackups(); }) .then((backups) => { expect(backups).to.be.an('array'); expect(backups.length).to.be.at.least(1); expect(backups[0]).to.have.property('filename'); expect(backups[0]).to.have.property('path'); // Clean up return Promise.all(backups.map(b => fs.promises.unlink(b.path).catch(() => {}))); }) .then(() => done()) .catch(done); }); it('Should restore from backup', (done) => { let backupPath; const testIP = '88.88.88.88'; // Use a unique IP // Setup once - make sure we're using the test file hostparty.setup({ path: hooks.path, autoBackup: false }); // Create backup of current state hostparty .createBackup() .then((bkPath) => { backupPath = bkPath; // Add a new IP return hostparty.add(testIP, ['restore-test.local']); }) .then(() => { return hostparty.list(); }) .then((hosts) => { // Verify the IP was added expect(hosts).to.have.property(testIP); // Now restore from backup return hostparty.restore(backupPath); }) .then(() => { return hostparty.list(); }) .then((hosts) => { // Verify the IP is no longer present (restored to pre-add state) expect(hosts).to.not.have.property(testIP); // Clean up backup file return fs.promises.unlink(backupPath); }) .then(() => done()) .catch(done); }); }); /** * move-hostname tests */ describe('Move hostname operations:', () => { const fs = require('fs'); const origPath = require('path').resolve('./tests/etc/hosts.test.orig'); beforeEach(() => { fs.writeFileSync(hooks.path, fs.readFileSync(origPath)); hostparty.setup({ path: hooks.path, force: false, autoBackup: false }); }); it('Should move a hostname to a new IP', (done) => { const hostname = 'caps.lol'; const fromIP = '1.2.3.4'; const toIP = '99.99.99.99'; hostparty .list() .then((hosts) => { expect(hosts[fromIP]).to.include(hostname); return hostparty.moveHostname(hostname, toIP); }) .then(() => { return hostparty.list(); }) .then((hosts) => { expect(hosts[fromIP]).to.not.include(hostname); expect(hosts[toIP]).to.include(hostname); done(); }) .catch(done); }); it('Should reject moving non-existent hostname', (done) => { hostparty .moveHostname('nonexistent.hostname', '1.2.3.4') .then(() => { done(new Error('Should have rejected non-existent hostname')); }) .catch(() => { done(); }); }); it('Should reject moving to same IP', (done) => { hostparty .moveHostname('caps.lol', '1.2.3.4') .then(() => { done(new Error('Should have rejected same IP')); }) .catch((err) => { expect(err).to.include('already at IP'); done(); }); }); }); /** * stats tests */ describe('Statistics operations:', () => { const fs = require('fs'); const origPath = require('path').resolve('./tests/etc/hosts.test.orig'); beforeEach(() => { fs.writeFileSync(hooks.path, fs.readFileSync(origPath)); hostparty.setup({ path: hooks.path, autoBackup: false }); }); it('Should return stats object with correct properties', (done) => { hostparty .getStats() .then((stats) => { expect(stats).to.have.property('activeIPs'); expect(stats).to.have.property('disabledIPs'); expect(stats).to.have.property('totalIPs'); expect(stats).to.have.property('totalHostnames'); expect(stats).to.have.property('uniqueHostnames'); expect(stats.activeIPs).to.be.a('number'); expect(stats.totalIPs).to.equal(stats.activeIPs + stats.disabledIPs); done(); }) .catch(done); }); it('Should count disabled IPs correctly', (done) => { hostparty .disable('10.20.30.40') .then(() => { return hostparty.getStats(); }) .then((stats) => { expect(stats.disabledIPs).to.equal(1); done(); }) .catch(done); }); }); })();