Add knex AliasesDB
This commit is contained in:
parent
76e0d1b7ec
commit
7ebf3c18ab
76
integration_test/db/aliases.js
Normal file
76
integration_test/db/aliases.js
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
const assert = require('assert');
|
||||||
|
const AliasesDB = require('../../lib/db/aliases').AliasesDB;
|
||||||
|
const testDB = require('../testutil/db').testDB;
|
||||||
|
|
||||||
|
const aliasesDB = new AliasesDB(testDB);
|
||||||
|
const testIPs = ['111.111.111.111', '111.111.111.222'];
|
||||||
|
const testNames = ['itest1', 'itest2'];
|
||||||
|
|
||||||
|
function cleanup() {
|
||||||
|
return testDB.knex.table('aliases')
|
||||||
|
.where('ip', 'in', testIPs)
|
||||||
|
.del()
|
||||||
|
.then(() => {
|
||||||
|
return testDB.knex.table('aliases')
|
||||||
|
.where('name', 'in', testNames)
|
||||||
|
.del();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function addSomeAliases() {
|
||||||
|
return cleanup().then(() => {
|
||||||
|
return testDB.knex.table('aliases')
|
||||||
|
.insert([
|
||||||
|
{ ip: testIPs[0], name: testNames[0] },
|
||||||
|
{ ip: testIPs[0], name: testNames[1] },
|
||||||
|
{ ip: testIPs[1], name: testNames[1] }
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('AliasesDB', () => {
|
||||||
|
describe('#addAlias', () => {
|
||||||
|
beforeEach(cleanup);
|
||||||
|
afterEach(cleanup);
|
||||||
|
|
||||||
|
it('adds a new alias', () => {
|
||||||
|
return aliasesDB.addAlias(testIPs[0], testNames[0])
|
||||||
|
.then(() => {
|
||||||
|
return testDB.knex.table('aliases')
|
||||||
|
.where({ ip: testIPs[0], name: testNames[0] })
|
||||||
|
.select()
|
||||||
|
.then(rows => {
|
||||||
|
assert.strictEqual(rows.length, 1, 'expected 1 row');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#getAliasesByIP', () => {
|
||||||
|
beforeEach(addSomeAliases);
|
||||||
|
afterEach(cleanup);
|
||||||
|
|
||||||
|
it('retrieves aliases by IP', () => {
|
||||||
|
return aliasesDB.getAliasesByIP(testIPs[0])
|
||||||
|
.then(names => assert.deepStrictEqual(
|
||||||
|
names.sort(), testNames.sort()));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('retrieves aliases by partial IP', () => {
|
||||||
|
return aliasesDB.getAliasesByIP(testIPs[0].substring(4))
|
||||||
|
.then(names => assert.deepStrictEqual(
|
||||||
|
names.sort(), testNames.sort()));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#getIPsByName', () => {
|
||||||
|
beforeEach(addSomeAliases);
|
||||||
|
afterEach(cleanup);
|
||||||
|
|
||||||
|
it('retrieves IPs by name', () => {
|
||||||
|
return aliasesDB.getIPsByName(testNames[1])
|
||||||
|
.then(ips => assert.deepStrictEqual(
|
||||||
|
ips.sort(), testIPs.sort()));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
59
src/db/aliases.js
Normal file
59
src/db/aliases.js
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
import { Database } from '../database';
|
||||||
|
import { LoggerFactory } from '@calzoneman/jsli';
|
||||||
|
import net from 'net';
|
||||||
|
|
||||||
|
const LOGGER = LoggerFactory.getLogger('AliasesDB');
|
||||||
|
|
||||||
|
class AliasesDB {
|
||||||
|
db: Database;
|
||||||
|
|
||||||
|
constructor(db: Database) {
|
||||||
|
this.db = db;
|
||||||
|
}
|
||||||
|
|
||||||
|
async addAlias(ip: string, name: string) {
|
||||||
|
return this.db.runTransaction(async tx => {
|
||||||
|
try {
|
||||||
|
await tx.table('aliases')
|
||||||
|
.where({ ip, name })
|
||||||
|
.del();
|
||||||
|
await tx.table('aliases')
|
||||||
|
.insert({ ip, name, time: Date.now() });
|
||||||
|
} catch (error) {
|
||||||
|
LOGGER.error('Failed to save alias: %s (ip=%s, name=%s)',
|
||||||
|
error.message, ip, name);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async getAliasesByIP(ip: string): Promise<Array<string>> {
|
||||||
|
return this.db.runTransaction(async tx => {
|
||||||
|
const query = tx.table('aliases');
|
||||||
|
if (net.isIP(ip)) {
|
||||||
|
query.where({ ip: ip })
|
||||||
|
} else {
|
||||||
|
const delimiter = /^[0-9]+\./.test(ip) ? '.' : ':';
|
||||||
|
query.where('ip', 'LIKE', ip + delimiter + '%');
|
||||||
|
}
|
||||||
|
|
||||||
|
const rows = await query.select()
|
||||||
|
.distinct('name')
|
||||||
|
.orderBy('time', 'desc')
|
||||||
|
.limit(5);
|
||||||
|
return rows.map(row => row.name);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async getIPsByName(name: string): Promise<Array<string>> {
|
||||||
|
return this.db.runTransaction(async tx => {
|
||||||
|
const rows = await tx.table('aliases')
|
||||||
|
.select('ip')
|
||||||
|
.where({ name });
|
||||||
|
return rows.map(row => row.ip);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { AliasesDB };
|
108
test/db/aliases.js
Normal file
108
test/db/aliases.js
Normal file
|
@ -0,0 +1,108 @@
|
||||||
|
const assert = require('assert');
|
||||||
|
const sinon = require('sinon');
|
||||||
|
const TestUtilDB = require('../testutil/db');
|
||||||
|
const AliasesDB = require('../../lib/db/aliases').AliasesDB;
|
||||||
|
|
||||||
|
describe('AliasesDB', () => {
|
||||||
|
let mockTx, mockDB, aliasesDB;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
mockTx = new TestUtilDB.MockTx();
|
||||||
|
mockDB = new TestUtilDB.MockDB(mockTx);
|
||||||
|
aliasesDB = new AliasesDB(mockDB);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#addAlias', () => {
|
||||||
|
it('adds a new alias', () => {
|
||||||
|
const ip = '1.2.3.4';
|
||||||
|
const name = 'foo';
|
||||||
|
sinon.stub(mockTx, 'table').withArgs('aliases').returns(mockTx);
|
||||||
|
sinon.stub(mockTx, 'where').withArgs({ ip, name }).returns(mockTx);
|
||||||
|
const del = sinon.stub(mockTx, 'del').resolves();
|
||||||
|
const insert = sinon.stub(mockTx, 'insert').resolves();
|
||||||
|
return aliasesDB.addAlias(ip, name).then(() => {
|
||||||
|
assert(del.called, 'Expected old alias to be purged');
|
||||||
|
assert(insert.called, 'Expected new alias to be inserted');
|
||||||
|
const record = insert.getCall(0).args[0];
|
||||||
|
assert.strictEqual(record.ip, ip);
|
||||||
|
assert.strictEqual(record.name, name);
|
||||||
|
assert(typeof record.time === 'number', 'Expected time field to be a number');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#getAliasesByIP', () => {
|
||||||
|
it('retrieves aliases by full IP', () => {
|
||||||
|
const ip = '1.2.3.4';
|
||||||
|
const rows = [
|
||||||
|
{ ip, name: 'foo' },
|
||||||
|
{ ip, name: 'bar' }
|
||||||
|
];
|
||||||
|
|
||||||
|
sinon.stub(mockTx, 'table').withArgs('aliases').returns(mockTx);
|
||||||
|
sinon.stub(mockTx, 'where').withArgs({ ip }).returns(mockTx);
|
||||||
|
sinon.stub(mockTx, 'select').returns(mockTx);
|
||||||
|
sinon.stub(mockTx, 'distinct').withArgs('name').returns(mockTx);
|
||||||
|
sinon.stub(mockTx, 'orderBy').withArgs('time', 'desc').returns(mockTx);
|
||||||
|
sinon.stub(mockTx, 'limit').withArgs(5).resolves(rows);
|
||||||
|
return aliasesDB.getAliasesByIP(ip).then(names => {
|
||||||
|
assert.deepStrictEqual(names.sort(), ['bar', 'foo']);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('retrieves aliases by IPv4 range', () => {
|
||||||
|
const ip = '1.2.3';
|
||||||
|
const rows = [
|
||||||
|
{ ip: ip + '.4', name: 'foo' },
|
||||||
|
{ ip: ip + '.5', name: 'bar' }
|
||||||
|
];
|
||||||
|
|
||||||
|
sinon.stub(mockTx, 'table').withArgs('aliases').returns(mockTx);
|
||||||
|
sinon.stub(mockTx, 'where').withArgs('ip', 'LIKE', `${ip}.%`).returns(mockTx);
|
||||||
|
sinon.stub(mockTx, 'select').returns(mockTx);
|
||||||
|
sinon.stub(mockTx, 'distinct').withArgs('name').returns(mockTx);
|
||||||
|
sinon.stub(mockTx, 'orderBy').withArgs('time', 'desc').returns(mockTx);
|
||||||
|
sinon.stub(mockTx, 'limit').withArgs(5).resolves(rows);
|
||||||
|
return aliasesDB.getAliasesByIP(ip).then(names => {
|
||||||
|
assert.deepStrictEqual(names.sort(), ['bar', 'foo']);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('retrieves aliases by IPv6 range', () => {
|
||||||
|
const ip = '1:2:3';
|
||||||
|
const rows = [
|
||||||
|
{ ip: ip + '::4', name: 'foo' },
|
||||||
|
{ ip: ip + '::5', name: 'bar' }
|
||||||
|
];
|
||||||
|
|
||||||
|
sinon.stub(mockTx, 'table').withArgs('aliases').returns(mockTx);
|
||||||
|
const where = sinon.stub(mockTx, 'where')
|
||||||
|
.withArgs('ip', 'LIKE', `${ip}:%`).returns(mockTx);
|
||||||
|
sinon.stub(mockTx, 'select').returns(mockTx);
|
||||||
|
sinon.stub(mockTx, 'distinct').withArgs('name').returns(mockTx);
|
||||||
|
sinon.stub(mockTx, 'orderBy').withArgs('time', 'desc').returns(mockTx);
|
||||||
|
sinon.stub(mockTx, 'limit').withArgs(5).resolves(rows);
|
||||||
|
return aliasesDB.getAliasesByIP(ip).then(names => {
|
||||||
|
assert(where.called, 'Expected WHERE LIKE clause');
|
||||||
|
assert.deepStrictEqual(names.sort(), ['bar', 'foo']);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#getIPsByName', () => {
|
||||||
|
it('retrieves IPs by name', () => {
|
||||||
|
const name = 'foo';
|
||||||
|
const rows = [
|
||||||
|
{ name, ip: '1.2.3.4' },
|
||||||
|
{ name, ip: '5.6.7.8' }
|
||||||
|
];
|
||||||
|
|
||||||
|
sinon.stub(mockTx, 'table').withArgs('aliases').returns(mockTx);
|
||||||
|
sinon.stub(mockTx, 'select').withArgs('ip').returns(mockTx);
|
||||||
|
sinon.stub(mockTx, 'where').withArgs({ name }).resolves(rows);
|
||||||
|
return aliasesDB.getIPsByName(name).then(ips => {
|
||||||
|
assert.deepStrictEqual(ips.sort(), ['1.2.3.4', '5.6.7.8']);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -14,7 +14,17 @@ function MockTx() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
['insert', 'update', 'select', 'del', 'where', 'table'].forEach(method => {
|
[
|
||||||
|
'del',
|
||||||
|
'distinct',
|
||||||
|
'insert',
|
||||||
|
'limit',
|
||||||
|
'orderBy',
|
||||||
|
'select',
|
||||||
|
'table',
|
||||||
|
'update',
|
||||||
|
'where',
|
||||||
|
].forEach(method => {
|
||||||
MockTx.prototype[method] = function () {
|
MockTx.prototype[method] = function () {
|
||||||
return Promise.reject(new Error(`No stub defined for method "${method}"`));
|
return Promise.reject(new Error(`No stub defined for method "${method}"`));
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue