Merge pull request #1240 from balena-io/tests-js

Convert some tests to javascript
This commit is contained in:
Page- 2020-04-02 18:07:37 +01:00 committed by GitHub
commit 67b22cee54
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 908 additions and 789 deletions

View File

@ -94,7 +94,9 @@ export class Network {
public static fromComposeObject(
name: string,
appId: number,
network: Partial<ComposeNetworkConfig>,
network: Partial<Omit<ComposeNetworkConfig, 'ipam'>> & {
ipam?: Partial<ComposeNetworkConfig['ipam']>;
},
opts: NetworkOptions,
): Network {
const net = new Network(opts);

View File

@ -18,7 +18,7 @@ export interface DockerPortOptions {
portBindings: PortBindings;
}
interface PortRange {
export interface PortRange {
internalStart: number;
internalEnd: number;
externalStart: number;

View File

@ -1,116 +0,0 @@
https = require 'https'
stream = require 'stream'
zlib = require 'zlib'
Promise = require 'bluebird'
{ expect } = require './lib/chai-config'
sinon = require 'sinon'
{ stub } = sinon
{ Logger } = require '../src/logger'
{ ContainerLogs } = require '../src/logging/container'
describe 'Logger', ->
beforeEach ->
@_req = new stream.PassThrough()
@_req.flushHeaders = sinon.spy()
@_req.end = sinon.spy()
@_req.body = ''
@_req
.pipe(zlib.createGunzip())
.on 'data', (chunk) =>
@_req.body += chunk
stub(https, 'request').returns(@_req)
@fakeEventTracker = {
track: sinon.spy()
}
@logger = new Logger({ eventTracker: @fakeEventTracker })
@logger.init({
apiEndpoint: 'https://example.com'
uuid: 'deadbeef'
deviceApiKey: 'secretkey'
unmanaged: false
enableLogs: true
localMode: false
})
afterEach ->
https.request.restore()
it 'waits the grace period before sending any logs', ->
clock = sinon.useFakeTimers()
@logger.log({ message: 'foobar', serviceId: 15 })
clock.tick(4999)
clock.restore()
Promise.delay(100)
.then =>
expect(@_req.body).to.equal('')
it 'tears down the connection after inactivity', ->
clock = sinon.useFakeTimers()
@logger.log({ message: 'foobar', serviceId: 15 })
clock.tick(61000)
clock.restore()
Promise.delay(100)
.then =>
expect(@_req.end.calledOnce).to.be.true
it 'sends logs as gzipped ndjson', ->
timestamp = Date.now()
@logger.log({ message: 'foobar', serviceId: 15 })
@logger.log({ timestamp: 1337, message: 'foobar', serviceId: 15 })
@logger.log({ message: 'foobar' }) # shold be ignored
Promise.delay(5500).then =>
expect(https.request.calledOnce).to.be.true
opts = https.request.firstCall.args[0]
expect(opts.href).to.equal('https://example.com/device/v2/deadbeef/log-stream')
expect(opts.method).to.equal('POST')
expect(opts.headers).to.deep.equal({
'Authorization': 'Bearer secretkey'
'Content-Type': 'application/x-ndjson'
'Content-Encoding': 'gzip'
})
lines = @_req.body.split('\n')
expect(lines.length).to.equal(3)
expect(lines[2]).to.equal('')
msg = JSON.parse(lines[0])
expect(msg).to.have.property('message').that.equals('foobar')
expect(msg).to.have.property('serviceId').that.equals(15)
expect(msg).to.have.property('timestamp').that.is.at.least(timestamp)
msg = JSON.parse(lines[1])
expect(msg).to.deep.equal({ timestamp: 1337, message: 'foobar', serviceId: 15 })
it 'allows logging system messages which are also reported to the eventTracker', ->
timestamp = Date.now()
@logger.logSystemMessage('Hello there!', { someProp: 'someVal' }, 'Some event name')
Promise.delay(5500)
.then =>
expect(@fakeEventTracker.track).to.be.calledWith('Some event name', { someProp: 'someVal' })
lines = @_req.body.split('\n')
expect(lines.length).to.equal(2)
expect(lines[1]).to.equal('')
msg = JSON.parse(lines[0])
expect(msg).to.have.property('message').that.equals('Hello there!')
expect(msg).to.have.property('isSystem').that.equals(true)
expect(msg).to.have.property('timestamp').that.is.at.least(timestamp)
it 'should support non-tty log lines', ->
message = '\u0001\u0000\u0000\u0000\u0000\u0000\u0000?2018-09-21T12:37:09.819134000Z this is the message'
buffer = Buffer.from(message)
expect(ContainerLogs.extractMessage(buffer)).to.deep.equal({
message: 'this is the message',
timestamp: 1537533429819
})

148
test/12-logger.spec.js Normal file
View File

@ -0,0 +1,148 @@
import * as https from 'https';
import * as stream from 'stream';
import * as zlib from 'zlib';
import * as Promise from 'bluebird';
import { expect } from './lib/chai-config';
import * as sinon from 'sinon';
import { Logger } from '../src/logger';
import { ContainerLogs } from '../src/logging/container';
describe('Logger', function() {
beforeEach(function() {
this._req = new stream.PassThrough();
this._req.flushHeaders = sinon.spy();
this._req.end = sinon.spy();
this._req.body = '';
this._req.pipe(zlib.createGunzip()).on('data', chunk => {
this._req.body += chunk;
});
this.requestStub = sinon.stub(https, 'request').returns(this._req);
this.fakeEventTracker = {
track: sinon.spy(),
};
// @ts-ignore missing db property
this.logger = new Logger({ eventTracker: this.fakeEventTracker });
return this.logger.init({
apiEndpoint: 'https://example.com',
uuid: 'deadbeef',
deviceApiKey: 'secretkey',
unmanaged: false,
enableLogs: true,
localMode: false,
});
});
afterEach(function() {
this.requestStub.restore();
});
it('waits the grace period before sending any logs', function() {
const clock = sinon.useFakeTimers();
this.logger.log({ message: 'foobar', serviceId: 15 });
clock.tick(4999);
clock.restore();
return Promise.delay(100).then(() => {
expect(this._req.body).to.equal('');
});
});
it('tears down the connection after inactivity', function() {
const clock = sinon.useFakeTimers();
this.logger.log({ message: 'foobar', serviceId: 15 });
clock.tick(61000);
clock.restore();
return Promise.delay(100).then(() => {
expect(this._req.end.calledOnce).to.be.true;
});
});
it('sends logs as gzipped ndjson', function() {
const timestamp = Date.now();
this.logger.log({ message: 'foobar', serviceId: 15 });
this.logger.log({ timestamp: 1337, message: 'foobar', serviceId: 15 });
this.logger.log({ message: 'foobar' }); // shold be ignored
return Promise.delay(5500).then(() => {
expect(this.requestStub.calledOnce).to.be.true;
const opts = this.requestStub.firstCall.args[0];
expect(opts.href).to.equal(
'https://example.com/device/v2/deadbeef/log-stream',
);
expect(opts.method).to.equal('POST');
expect(opts.headers).to.deep.equal({
Authorization: 'Bearer secretkey',
'Content-Type': 'application/x-ndjson',
'Content-Encoding': 'gzip',
});
const lines = this._req.body.split('\n');
expect(lines.length).to.equal(3);
expect(lines[2]).to.equal('');
let msg = JSON.parse(lines[0]);
expect(msg)
.to.have.property('message')
.that.equals('foobar');
expect(msg)
.to.have.property('serviceId')
.that.equals(15);
expect(msg)
.to.have.property('timestamp')
.that.is.at.least(timestamp);
msg = JSON.parse(lines[1]);
expect(msg).to.deep.equal({
timestamp: 1337,
message: 'foobar',
serviceId: 15,
});
});
});
it('allows logging system messages which are also reported to the eventTracker', function() {
const timestamp = Date.now();
this.logger.logSystemMessage(
'Hello there!',
{ someProp: 'someVal' },
'Some event name',
);
return Promise.delay(5500).then(() => {
expect(this.fakeEventTracker.track).to.be.calledWith('Some event name', {
someProp: 'someVal',
});
const lines = this._req.body.split('\n');
expect(lines.length).to.equal(2);
expect(lines[1]).to.equal('');
const msg = JSON.parse(lines[0]);
expect(msg)
.to.have.property('message')
.that.equals('Hello there!');
expect(msg)
.to.have.property('isSystem')
.that.equals(true);
expect(msg)
.to.have.property('timestamp')
.that.is.at.least(timestamp);
});
});
it('should support non-tty log lines', function() {
const message =
'\u0001\u0000\u0000\u0000\u0000\u0000\u0000?2018-09-21T12:37:09.819134000Z this is the message';
const buffer = Buffer.from(message);
// @ts-ignore accessing a private function
expect(ContainerLogs.extractMessage(buffer)).to.deep.equal({
message: 'this is the message',
timestamp: 1537533429819,
});
});
});

View File

@ -1,52 +0,0 @@
{ expect } = require './lib/chai-config'
conversion = require '../src/lib/conversions'
describe 'conversions', ->
describe 'envArrayToObject', ->
it 'should convert an env array to an object', ->
expect(conversion.envArrayToObject([
'key=value'
'test1=test2'
'k=v'
'equalsvalue=thisvaluehasan=char'
'asd='
'number=123'
])).to.deep.equal({
key: 'value'
test1: 'test2'
k: 'v'
equalsvalue: 'thisvaluehasan=char'
asd: ''
number: '123'
})
it 'should ignore invalid env array entries', ->
expect(conversion.envArrayToObject([
'key1',
'key1=value1'
])).to.deep.equal({
key1: 'value1'
})
it 'should return an empty object with an empty input', ->
expect(conversion.envArrayToObject(null)).to.deep.equal({})
expect(conversion.envArrayToObject('')).to.deep.equal({})
expect(conversion.envArrayToObject([])).to.deep.equal({})
expect(conversion.envArrayToObject(1)).to.deep.equal({})
it 'should correctly handle whitespace', ->
expect(conversion.envArrayToObject([
'key1= test',
'key2=test\ntest',
'key3=test ',
'key4= test '
'key5=test\r\ntest',
])).to.deep.equal({
key1: ' test',
key2: 'test\ntest',
key3: 'test ',
key4: ' test ',
key5: 'test\r\ntest'
})

View File

@ -0,0 +1,59 @@
import { expect } from './lib/chai-config';
import * as conversion from '../src/lib/conversions';
describe('conversions', function() {
describe('envArrayToObject', function() {
it('should convert an env array to an object', () =>
expect(
conversion.envArrayToObject([
'key=value',
'test1=test2',
'k=v',
'equalsvalue=thisvaluehasan=char',
'asd=',
'number=123',
]),
).to.deep.equal({
key: 'value',
test1: 'test2',
k: 'v',
equalsvalue: 'thisvaluehasan=char',
asd: '',
number: '123',
}));
it('should ignore invalid env array entries', () =>
expect(
conversion.envArrayToObject(['key1', 'key1=value1']),
).to.deep.equal({
key1: 'value1',
}));
it('should return an empty object with an empty input', function() {
// @ts-ignore passing invalid value to test
expect(conversion.envArrayToObject(null)).to.deep.equal({});
// @ts-ignore passing invalid value to test
expect(conversion.envArrayToObject('')).to.deep.equal({});
expect(conversion.envArrayToObject([])).to.deep.equal({});
// @ts-ignore passing invalid value to test
expect(conversion.envArrayToObject(1)).to.deep.equal({});
});
});
it('should correctly handle whitespace', () =>
expect(
conversion.envArrayToObject([
'key1= test',
'key2=test\ntest',
'key3=test ',
'key4= test ',
'key5=test\r\ntest',
]),
).to.deep.equal({
key1: ' test',
key2: 'test\ntest',
key3: 'test ',
key4: ' test ',
key5: 'test\r\ntest',
}));
});

View File

@ -1,357 +0,0 @@
{ expect } = require './lib/chai-config'
{ PortMap } = require '../src/compose/ports'
describe 'Ports', ->
describe 'Port string parsing', ->
it 'should correctly parse a port string without a range', ->
expect(new PortMap('80')).to.deep.equal(new PortMap({
internalStart: 80
internalEnd: 80
externalStart: 80
externalEnd: 80
protocol: 'tcp'
host: ''
}))
expect(new PortMap('80:80')).to.deep.equal(new PortMap({
internalStart: 80
internalEnd: 80
externalStart: 80
externalEnd: 80
protocol: 'tcp'
host: ''
}))
it 'should correctly parse a port string without an external range', ->
expect(new PortMap('80-90')).to.deep.equal(new PortMap({
internalStart: 80
internalEnd: 90
externalStart: 80
externalEnd: 90
protocol: 'tcp'
host: ''
}))
it 'should correctly parse a port string with a range', ->
expect(new PortMap('80-100:100-120')).to.deep.equal(new PortMap({
internalStart: 100
internalEnd: 120
externalStart: 80
externalEnd: 100
protocol: 'tcp'
host: ''
}))
it 'should correctly parse a protocol', ->
expect(new PortMap('80/udp')).to.deep.equal(new PortMap({
internalStart: 80
internalEnd: 80
externalStart: 80
externalEnd: 80
protocol: 'udp'
host: ''
}))
expect(new PortMap('80:80/udp')).to.deep.equal(new PortMap({
internalStart: 80
internalEnd: 80
externalStart: 80
externalEnd: 80
protocol: 'udp'
host: ''
}))
expect(new PortMap('80-90:100-110/udp')).to.deep.equal(new PortMap({
internalStart: 100
internalEnd: 110
externalStart: 80
externalEnd: 90
protocol: 'udp'
host: ''
}))
it 'should throw when the port string is incorrect', ->
expect(-> new PortMap('80-90:80-85')).to.throw
describe 'toDockerOpts', ->
it 'should correctly generate docker options', ->
expect(new PortMap('80').toDockerOpts()).to.deep.equal({
exposedPorts: {
'80/tcp': {}
}
portBindings: {
'80/tcp': [{ HostIp: '', HostPort: '80' }]
}
})
it 'should correctly generate docker options for a port range', ->
expect(new PortMap('80-85').toDockerOpts()).to.deep.equal({
exposedPorts: {
'80/tcp': {}
'81/tcp': {}
'82/tcp': {}
'83/tcp': {}
'84/tcp': {}
'85/tcp': {}
}
portBindings: {
'80/tcp': [{ HostIp: '', HostPort: '80' }]
'81/tcp': [{ HostIp: '', HostPort: '81' }]
'82/tcp': [{ HostIp: '', HostPort: '82' }]
'83/tcp': [{ HostIp: '', HostPort: '83' }]
'84/tcp': [{ HostIp: '', HostPort: '84' }]
'85/tcp': [{ HostIp: '', HostPort: '85' }]
}
})
describe 'fromDockerOpts', ->
it 'should correctly detect a port range', ->
expect(PortMap.fromDockerOpts({
'100/tcp': [{ HostIp: '123', HostPort: 200 }]
'101/tcp': [{ HostIp: '123', HostPort: 201 }]
'102/tcp': [{ HostIp: '123', HostPort: 202 }]
})).to.deep.equal([new PortMap({
internalStart: 100
internalEnd: 102
externalStart: 200
externalEnd: 202
protocol: 'tcp'
host: '123'
})])
it 'should correctly split ports into ranges', ->
expect(PortMap.fromDockerOpts({
'100/tcp': [{ HostIp: '123', HostPort: 200 }]
'101/tcp': [{ HostIp: '123', HostPort: 201 }]
'105/tcp': [{ HostIp: '123', HostPort: 205 }]
'106/tcp': [{ HostIp: '123', HostPort: 206 }]
'110/tcp': [{ HostIp: '123', HostPort: 210 }]
})).to.deep.equal([
new PortMap({
internalStart: 100
internalEnd: 101
externalStart: 200
externalEnd: 201
protocol: 'tcp'
host: '123'
})
new PortMap({
internalStart: 105
internalEnd: 106
externalStart: 205
externalEnd: 206
protocol: 'tcp'
host: '123'
})
new PortMap({
internalStart: 110
internalEnd: 110
externalStart: 210
externalEnd: 210
protocol: 'tcp'
host: '123'
})
])
it 'should correctly consider internal and external ports', ->
expect(PortMap.fromDockerOpts({
'100/tcp': [{ HostIp: '123', HostPort: 200 }]
'101/tcp': [{ HostIp: '123', HostPort: 101 }]
'102/tcp': [{ HostIp: '123', HostPort: 202 }]
})).to.deep.equal([
new PortMap({
internalStart: 100
internalEnd: 100
externalStart: 200
externalEnd: 200
protocol: 'tcp'
host: '123'
})
new PortMap({
internalStart: 101
internalEnd: 101
externalStart: 101
externalEnd: 101
protocol: 'tcp'
host: '123'
})
new PortMap({
internalStart: 102
internalEnd: 102
externalStart: 202
externalEnd: 202
protocol: 'tcp'
host: '123'
})
])
it 'should consider the host when generating ranges', ->
expect(PortMap.fromDockerOpts({
'100/tcp': [{ HostIp: '123', HostPort: 200 }]
'101/tcp': [{ HostIp: '456', HostPort: 201 }]
'102/tcp': [{ HostIp: '456', HostPort: 202 }]
})).to.deep.equal([
new PortMap({
internalStart: 100
internalEnd: 100
externalStart: 200
externalEnd: 200
protocol: 'tcp'
host: '123'
})
new PortMap({
internalStart: 101
internalEnd: 102
externalStart: 201
externalEnd: 202
protocol: 'tcp'
host: '456'
})
])
it 'should consider the protocol when generating ranges', ->
expect(PortMap.fromDockerOpts({
'100/tcp': [{ HostIp: '123', HostPort: 200 }]
'101/udp': [{ HostIp: '123', HostPort: 201 }]
'102/udp': [{ HostIp: '123', HostPort: 202 }]
})).to.deep.equal([
new PortMap({
internalStart: 100
internalEnd: 100
externalStart: 200
externalEnd: 200
protocol: 'tcp'
host: '123'
})
new PortMap({
internalStart: 101
internalEnd: 102
externalStart: 201
externalEnd: 202
protocol: 'udp'
host: '123'
})
])
it 'should correctly detect multiple hosts ports on an internal port', ->
expect(PortMap.fromDockerOpts({
'100/tcp': [{ HostIp: '123', HostPort: 200 }, { HostIp: '123', HostPort: 201 }]
})).to.deep.equal([
new PortMap({
internalStart: 100,
internalEnd: 100,
externalStart: 200,
externalEnd: 200,
protocol: 'tcp',
host: '123'
})
new PortMap({
internalStart: 100,
internalEnd: 100,
externalStart: 201,
externalEnd: 201,
protocol: 'tcp',
host: '123'
})
])
describe 'Running container comparison', ->
it 'should not consider order when comparing current and target state', ->
portBindings = require('./data/ports/not-ascending/port-bindings.json')
compose = require('./data/ports/not-ascending/compose.json')
portMapsCurrent = PortMap.fromDockerOpts(portBindings)
portMapsTarget = PortMap.fromComposePorts(compose.ports)
expect(portMapsTarget).to.deep.equal(portMapsCurrent)
describe 'fromComposePorts', ->
it 'should normalise compose ports', ->
expect(PortMap.fromComposePorts([
'80:80',
'81:81',
'82:82',
])).to.deep.equal([
new PortMap('80-82')
])
describe 'normalisePortMaps', ->
it 'should correctly normalise PortMap lists', ->
expect(PortMap.normalisePortMaps([
new PortMap('80:90')
new PortMap('81:91')
])).to.deep.equal([
new PortMap('80-81:90-91')
])
expect(PortMap.normalisePortMaps([
new PortMap('80:90')
new PortMap('81:91')
new PortMap('82:92')
new PortMap('83:93')
new PortMap('84:94')
new PortMap('85:95')
new PortMap('86:96')
new PortMap('87:97')
new PortMap('88:98')
new PortMap('89:99')
new PortMap('90:100')
])).to.deep.equal([
new PortMap('80-90:90-100')
])
expect(PortMap.normalisePortMaps([])).to.deep.equal([])
it 'should correctly consider protocols', ->
expect(PortMap.normalisePortMaps([
new PortMap('80:90')
new PortMap('81:91/udp')
])).to.deep.equal([
new PortMap('80:90')
new PortMap('81:91/udp')
])
expect(PortMap.normalisePortMaps([
new PortMap('80:90')
new PortMap('100:110/udp')
new PortMap('81:91')
])).to.deep.equal([
new PortMap('80-81:90-91')
new PortMap('100:110/udp')
])
# This shouldn't ever be provided, but it shows the algorithm
# working properly
expect(PortMap.normalisePortMaps([
new PortMap('80:90')
new PortMap('81:91/udp')
new PortMap('81:91')
])).to.deep.equal([
new PortMap('80-81:90-91')
new PortMap('81:91/udp')
])
it 'should correctly consider hosts', ->
expect(PortMap.normalisePortMaps([
new PortMap('127.0.0.1:80:80')
new PortMap('81:81')
])).to.deep.equal([
new PortMap('127.0.0.1:80:80')
new PortMap('81:81')
])
expect(PortMap.normalisePortMaps([
new PortMap('127.0.0.1:80:80')
new PortMap('127.0.0.1:81:81')
])).to.deep.equal([
new PortMap('127.0.0.1:80-81:80-81')
])

398
test/16-ports.spec.ts Normal file
View File

@ -0,0 +1,398 @@
import { PortMap, PortRange } from '../src/compose/ports';
import { expect } from './lib/chai-config';
// Force cast `PortMap` as a public version so we can test it
const PortMapPublic = (PortMap as any) as new (
portStrOrObj: string | PortRange,
) => PortMap;
describe('Ports', function() {
describe('Port string parsing', function() {
it('should correctly parse a port string without a range', function() {
expect(new PortMapPublic('80')).to.deep.equal(
new PortMapPublic({
internalStart: 80,
internalEnd: 80,
externalStart: 80,
externalEnd: 80,
protocol: 'tcp',
host: '',
}),
);
expect(new PortMapPublic('80:80')).to.deep.equal(
new PortMapPublic({
internalStart: 80,
internalEnd: 80,
externalStart: 80,
externalEnd: 80,
protocol: 'tcp',
host: '',
}),
);
});
it('should correctly parse a port string without an external range', () =>
expect(new PortMapPublic('80-90')).to.deep.equal(
new PortMapPublic({
internalStart: 80,
internalEnd: 90,
externalStart: 80,
externalEnd: 90,
protocol: 'tcp',
host: '',
}),
));
it('should correctly parse a port string with a range', () =>
expect(new PortMapPublic('80-100:100-120')).to.deep.equal(
new PortMapPublic({
internalStart: 100,
internalEnd: 120,
externalStart: 80,
externalEnd: 100,
protocol: 'tcp',
host: '',
}),
));
it('should correctly parse a protocol', function() {
expect(new PortMapPublic('80/udp')).to.deep.equal(
new PortMapPublic({
internalStart: 80,
internalEnd: 80,
externalStart: 80,
externalEnd: 80,
protocol: 'udp',
host: '',
}),
);
expect(new PortMapPublic('80:80/udp')).to.deep.equal(
new PortMapPublic({
internalStart: 80,
internalEnd: 80,
externalStart: 80,
externalEnd: 80,
protocol: 'udp',
host: '',
}),
);
expect(new PortMapPublic('80-90:100-110/udp')).to.deep.equal(
new PortMapPublic({
internalStart: 100,
internalEnd: 110,
externalStart: 80,
externalEnd: 90,
protocol: 'udp',
host: '',
}),
);
});
it('should throw when the port string is incorrect', () =>
expect(() => new PortMapPublic('80-90:80-85')).to.throw);
});
describe('toDockerOpts', function() {
it('should correctly generate docker options', () =>
expect(new PortMapPublic('80').toDockerOpts()).to.deep.equal({
exposedPorts: {
'80/tcp': {},
},
portBindings: {
'80/tcp': [{ HostIp: '', HostPort: '80' }],
},
}));
it('should correctly generate docker options for a port range', () =>
expect(new PortMapPublic('80-85').toDockerOpts()).to.deep.equal({
exposedPorts: {
'80/tcp': {},
'81/tcp': {},
'82/tcp': {},
'83/tcp': {},
'84/tcp': {},
'85/tcp': {},
},
portBindings: {
'80/tcp': [{ HostIp: '', HostPort: '80' }],
'81/tcp': [{ HostIp: '', HostPort: '81' }],
'82/tcp': [{ HostIp: '', HostPort: '82' }],
'83/tcp': [{ HostIp: '', HostPort: '83' }],
'84/tcp': [{ HostIp: '', HostPort: '84' }],
'85/tcp': [{ HostIp: '', HostPort: '85' }],
},
}));
});
describe('fromDockerOpts', function() {
it('should correctly detect a port range', () =>
expect(
PortMap.fromDockerOpts({
'100/tcp': [{ HostIp: '123', HostPort: '200' }],
'101/tcp': [{ HostIp: '123', HostPort: '201' }],
'102/tcp': [{ HostIp: '123', HostPort: '202' }],
}),
).to.deep.equal([
new PortMapPublic({
internalStart: 100,
internalEnd: 102,
externalStart: 200,
externalEnd: 202,
protocol: 'tcp',
host: '123',
}),
]));
it('should correctly split ports into ranges', () =>
expect(
PortMap.fromDockerOpts({
'100/tcp': [{ HostIp: '123', HostPort: '200' }],
'101/tcp': [{ HostIp: '123', HostPort: '201' }],
'105/tcp': [{ HostIp: '123', HostPort: '205' }],
'106/tcp': [{ HostIp: '123', HostPort: '206' }],
'110/tcp': [{ HostIp: '123', HostPort: '210' }],
}),
).to.deep.equal([
new PortMapPublic({
internalStart: 100,
internalEnd: 101,
externalStart: 200,
externalEnd: 201,
protocol: 'tcp',
host: '123',
}),
new PortMapPublic({
internalStart: 105,
internalEnd: 106,
externalStart: 205,
externalEnd: 206,
protocol: 'tcp',
host: '123',
}),
new PortMapPublic({
internalStart: 110,
internalEnd: 110,
externalStart: 210,
externalEnd: 210,
protocol: 'tcp',
host: '123',
}),
]));
it('should correctly consider internal and external ports', () =>
expect(
PortMap.fromDockerOpts({
'100/tcp': [{ HostIp: '123', HostPort: '200' }],
'101/tcp': [{ HostIp: '123', HostPort: '101' }],
'102/tcp': [{ HostIp: '123', HostPort: '202' }],
}),
).to.deep.equal([
new PortMapPublic({
internalStart: 100,
internalEnd: 100,
externalStart: 200,
externalEnd: 200,
protocol: 'tcp',
host: '123',
}),
new PortMapPublic({
internalStart: 101,
internalEnd: 101,
externalStart: 101,
externalEnd: 101,
protocol: 'tcp',
host: '123',
}),
new PortMapPublic({
internalStart: 102,
internalEnd: 102,
externalStart: 202,
externalEnd: 202,
protocol: 'tcp',
host: '123',
}),
]));
it('should consider the host when generating ranges', () =>
expect(
PortMap.fromDockerOpts({
'100/tcp': [{ HostIp: '123', HostPort: '200' }],
'101/tcp': [{ HostIp: '456', HostPort: '201' }],
'102/tcp': [{ HostIp: '456', HostPort: '202' }],
}),
).to.deep.equal([
new PortMapPublic({
internalStart: 100,
internalEnd: 100,
externalStart: 200,
externalEnd: 200,
protocol: 'tcp',
host: '123',
}),
new PortMapPublic({
internalStart: 101,
internalEnd: 102,
externalStart: 201,
externalEnd: 202,
protocol: 'tcp',
host: '456',
}),
]));
it('should consider the protocol when generating ranges', () =>
expect(
PortMap.fromDockerOpts({
'100/tcp': [{ HostIp: '123', HostPort: '200' }],
'101/udp': [{ HostIp: '123', HostPort: '201' }],
'102/udp': [{ HostIp: '123', HostPort: '202' }],
}),
).to.deep.equal([
new PortMapPublic({
internalStart: 100,
internalEnd: 100,
externalStart: 200,
externalEnd: 200,
protocol: 'tcp',
host: '123',
}),
new PortMapPublic({
internalStart: 101,
internalEnd: 102,
externalStart: 201,
externalEnd: 202,
protocol: 'udp',
host: '123',
}),
]));
it('should correctly detect multiple hosts ports on an internal port', () =>
expect(
PortMap.fromDockerOpts({
'100/tcp': [
{ HostIp: '123', HostPort: '200' },
{ HostIp: '123', HostPort: '201' },
],
}),
).to.deep.equal([
new PortMapPublic({
internalStart: 100,
internalEnd: 100,
externalStart: 200,
externalEnd: 200,
protocol: 'tcp',
host: '123',
}),
new PortMapPublic({
internalStart: 100,
internalEnd: 100,
externalStart: 201,
externalEnd: 201,
protocol: 'tcp',
host: '123',
}),
]));
});
describe('Running container comparison', () =>
it('should not consider order when comparing current and target state', function() {
const portBindings = require('./data/ports/not-ascending/port-bindings.json');
const compose = require('./data/ports/not-ascending/compose.json');
const portMapsCurrent = PortMap.fromDockerOpts(portBindings);
const portMapsTarget = PortMap.fromComposePorts(compose.ports);
expect(portMapsTarget).to.deep.equal(portMapsCurrent);
}));
describe('fromComposePorts', () =>
it('should normalise compose ports', () =>
expect(
PortMap.fromComposePorts(['80:80', '81:81', '82:82']),
).to.deep.equal([new PortMapPublic('80-82')])));
describe('normalisePortMaps', function() {
it('should correctly normalise PortMap lists', function() {
expect(
PortMap.normalisePortMaps([
new PortMapPublic('80:90'),
new PortMapPublic('81:91'),
]),
).to.deep.equal([new PortMapPublic('80-81:90-91')]);
expect(
PortMap.normalisePortMaps([
new PortMapPublic('80:90'),
new PortMapPublic('81:91'),
new PortMapPublic('82:92'),
new PortMapPublic('83:93'),
new PortMapPublic('84:94'),
new PortMapPublic('85:95'),
new PortMapPublic('86:96'),
new PortMapPublic('87:97'),
new PortMapPublic('88:98'),
new PortMapPublic('89:99'),
new PortMapPublic('90:100'),
]),
).to.deep.equal([new PortMapPublic('80-90:90-100')]);
expect(PortMap.normalisePortMaps([])).to.deep.equal([]);
});
it('should correctly consider protocols', function() {
expect(
PortMap.normalisePortMaps([
new PortMapPublic('80:90'),
new PortMapPublic('81:91/udp'),
]),
).to.deep.equal([
new PortMapPublic('80:90'),
new PortMapPublic('81:91/udp'),
]);
expect(
PortMap.normalisePortMaps([
new PortMapPublic('80:90'),
new PortMapPublic('100:110/udp'),
new PortMapPublic('81:91'),
]),
).to.deep.equal([
new PortMapPublic('80-81:90-91'),
new PortMapPublic('100:110/udp'),
]);
// This shouldn't ever be provided, but it shows the algorithm
// working properly
expect(
PortMap.normalisePortMaps([
new PortMapPublic('80:90'),
new PortMapPublic('81:91/udp'),
new PortMapPublic('81:91'),
]),
).to.deep.equal([
new PortMapPublic('80-81:90-91'),
new PortMapPublic('81:91/udp'),
]);
});
it('should correctly consider hosts', function() {
expect(
PortMap.normalisePortMaps([
new PortMapPublic('127.0.0.1:80:80'),
new PortMapPublic('81:81'),
]),
).to.deep.equal([
new PortMapPublic('127.0.0.1:80:80'),
new PortMapPublic('81:81'),
]);
expect(
PortMap.normalisePortMaps([
new PortMapPublic('127.0.0.1:80:80'),
new PortMapPublic('127.0.0.1:81:81'),
]),
).to.deep.equal([new PortMapPublic('127.0.0.1:80-81:80-81')]);
});
});
});

View File

@ -1,120 +0,0 @@
{ expect } = require './lib/chai-config'
{ stub } = require 'sinon'
{ fs } = require 'mz'
configUtils = require '../src/config/utils'
{ ExtlinuxConfigBackend, RPiConfigBackend } = require '../src/config/backend'
extlinuxBackend = new ExtlinuxConfigBackend()
rpiBackend = new RPiConfigBackend()
describe 'Config Utilities', ->
describe 'Boot config utilities', ->
describe 'Env <-> Config', ->
it 'correctly transforms environments to boot config objects', ->
bootConfig = configUtils.envToBootConfig(rpiBackend, {
HOST_CONFIG_initramfs: 'initramf.gz 0x00800000'
HOST_CONFIG_dtparam: '"i2c=on","audio=on"'
HOST_CONFIG_dtoverlay: '"ads7846","lirc-rpi,gpio_out_pin=17,gpio_in_pin=13"'
HOST_CONFIG_foobar: 'baz'
})
expect(bootConfig).to.deep.equal({
initramfs: 'initramf.gz 0x00800000'
dtparam: [ 'i2c=on', 'audio=on' ]
dtoverlay: [ 'ads7846', 'lirc-rpi,gpio_out_pin=17,gpio_in_pin=13' ]
foobar: 'baz'
})
describe 'TX2 boot config utilities', ->
it 'should parse a extlinux.conf file', ->
text = '''
DEFAULT primary
# Comment
TIMEOUT 30
MENU TITLE Boot Options
LABEL primary
MENU LABEL primary Image
LINUX /Image
APPEND ${cbootargs} ${resin_kernel_root} ro rootwait
'''
parsed = ExtlinuxConfigBackend.parseExtlinuxFile(text)
expect(parsed.globals).to.have.property('DEFAULT').that.equals('primary')
expect(parsed.globals).to.have.property('TIMEOUT').that.equals('30')
expect(parsed.globals).to.have.property('MENU TITLE').that.equals('Boot Options')
expect(parsed.labels).to.have.property('primary')
primary = parsed.labels.primary
expect(primary).to.have.property('MENU LABEL').that.equals('primary Image')
expect(primary).to.have.property('LINUX').that.equals('/Image')
expect(primary).to.have.property('APPEND').that.equals('${cbootargs} ${resin_kernel_root} ro rootwait')
it 'should parse multiple service entries', ->
text = '''
DEFAULT primary
# Comment
TIMEOUT 30
MENU TITLE Boot Options
LABEL primary
LINUX test1
APPEND test2
LABEL secondary
LINUX test3
APPEND test4
'''
parsed = ExtlinuxConfigBackend.parseExtlinuxFile(text)
expect(parsed.labels).to.have.property('primary').that.deep.equals({
LINUX: 'test1'
APPEND: 'test2'
})
expect(parsed.labels).to.have.property('secondary').that.deep.equals({
LINUX: 'test3'
APPEND: 'test4'
})
it 'should parse configuration options from an extlinux.conf file', ->
text = '''
DEFAULT primary
# Comment
TIMEOUT 30
MENU TITLE Boot Options
LABEL primary
MENU LABEL primary Image
LINUX /Image
APPEND ${cbootargs} ${resin_kernel_root} ro rootwait isolcpus=3
'''
stub(fs, 'readFile').resolves(text)
parsed = extlinuxBackend.getBootConfig()
expect(parsed).to.eventually.have.property('isolcpus').that.equals('3')
fs.readFile.restore()
text = '''
DEFAULT primary
# Comment
TIMEOUT 30
MENU TITLE Boot Options
LABEL primary
MENU LABEL primary Image
LINUX /Image
APPEND ${cbootargs} ${resin_kernel_root} ro rootwait isolcpus=3,4,5
'''
stub(fs, 'readFile').resolves(text)
parsed = extlinuxBackend.getBootConfig()
fs.readFile.restore()
expect(parsed).to.eventually.have.property('isolcpus').that.equals('3,4,5')

View File

@ -0,0 +1,142 @@
import { expect } from './lib/chai-config';
import { stub } from 'sinon';
import { fs } from 'mz';
import * as configUtils from '../src/config/utils';
import { ExtlinuxConfigBackend, RPiConfigBackend } from '../src/config/backend';
const extlinuxBackend = new ExtlinuxConfigBackend();
const rpiBackend = new RPiConfigBackend();
describe('Config Utilities', () =>
describe('Boot config utilities', function() {
describe('Env <-> Config', () =>
it('correctly transforms environments to boot config objects', function() {
const bootConfig = configUtils.envToBootConfig(rpiBackend, {
HOST_CONFIG_initramfs: 'initramf.gz 0x00800000',
HOST_CONFIG_dtparam: '"i2c=on","audio=on"',
HOST_CONFIG_dtoverlay:
'"ads7846","lirc-rpi,gpio_out_pin=17,gpio_in_pin=13"',
HOST_CONFIG_foobar: 'baz',
});
expect(bootConfig).to.deep.equal({
initramfs: 'initramf.gz 0x00800000',
dtparam: ['i2c=on', 'audio=on'],
dtoverlay: ['ads7846', 'lirc-rpi,gpio_out_pin=17,gpio_in_pin=13'],
foobar: 'baz',
});
}));
describe('TX2 boot config utilities', function() {
it('should parse a extlinux.conf file', function() {
const text = `\
DEFAULT primary
# Comment
TIMEOUT 30
MENU TITLE Boot Options
LABEL primary
MENU LABEL primary Image
LINUX /Image
APPEND \${cbootargs} \${resin_kernel_root} ro rootwait\
`;
// @ts-ignore accessing private method
const parsed = ExtlinuxConfigBackend.parseExtlinuxFile(text);
expect(parsed.globals)
.to.have.property('DEFAULT')
.that.equals('primary');
expect(parsed.globals)
.to.have.property('TIMEOUT')
.that.equals('30');
expect(parsed.globals)
.to.have.property('MENU TITLE')
.that.equals('Boot Options');
expect(parsed.labels).to.have.property('primary');
const { primary } = parsed.labels;
expect(primary)
.to.have.property('MENU LABEL')
.that.equals('primary Image');
expect(primary)
.to.have.property('LINUX')
.that.equals('/Image');
expect(primary)
.to.have.property('APPEND')
.that.equals('${cbootargs} ${resin_kernel_root} ro rootwait');
});
it('should parse multiple service entries', function() {
const text = `\
DEFAULT primary
# Comment
TIMEOUT 30
MENU TITLE Boot Options
LABEL primary
LINUX test1
APPEND test2
LABEL secondary
LINUX test3
APPEND test4\
`;
// @ts-ignore accessing private method
const parsed = ExtlinuxConfigBackend.parseExtlinuxFile(text);
expect(parsed.labels)
.to.have.property('primary')
.that.deep.equals({
LINUX: 'test1',
APPEND: 'test2',
});
expect(parsed.labels)
.to.have.property('secondary')
.that.deep.equals({
LINUX: 'test3',
APPEND: 'test4',
});
});
it('should parse configuration options from an extlinux.conf file', function() {
let text = `\
DEFAULT primary
# Comment
TIMEOUT 30
MENU TITLE Boot Options
LABEL primary
MENU LABEL primary Image
LINUX /Image
APPEND \${cbootargs} \${resin_kernel_root} ro rootwait isolcpus=3\
`;
let readFileStub = stub(fs, 'readFile').resolves(text);
let parsed = extlinuxBackend.getBootConfig();
expect(parsed)
.to.eventually.have.property('isolcpus')
.that.equals('3');
readFileStub.restore();
text = `\
DEFAULT primary
# Comment
TIMEOUT 30
MENU TITLE Boot Options
LABEL primary
MENU LABEL primary Image
LINUX /Image
APPEND \${cbootargs} \${resin_kernel_root} ro rootwait isolcpus=3,4,5\
`;
readFileStub = stub(fs, 'readFile').resolves(text);
parsed = extlinuxBackend.getBootConfig();
readFileStub.restore();
expect(parsed)
.to.eventually.have.property('isolcpus')
.that.equals('3,4,5');
});
});
}));

View File

@ -1,104 +0,0 @@
{ expect } = require './lib/chai-config'
{ Network } = require '../src/compose/network'
describe 'compose/network', ->
describe 'compose config -> internal config', ->
it 'should convert a compose configuration to an internal representation', ->
network = Network.fromComposeObject('test', 123, {
'driver': 'bridge',
'ipam': {
'driver': 'default',
'config': [
{
'subnet': '172.25.0.0/25',
'gateway': '172.25.0.1'
}
]
}
}, { logger: null, docker: null })
expect(network.config).to.deep.equal({
driver: 'bridge'
ipam: {
driver: 'default'
config: [
subnet: '172.25.0.0/25'
gateway: '172.25.0.1'
]
options: {}
}
enableIPv6: false,
internal: false,
labels: {}
options: {}
})
it 'should handle an incomplete ipam configuration', ->
network = Network.fromComposeObject('test', 123, {
ipam: {
config: [
{
subnet: '172.25.0.0/25',
gateway: '172.25.0.1'
}
]
}
}, { logger: null, docker: null })
expect(network.config).to.deep.equal({
driver: 'bridge',
enableIPv6: false,
internal: false,
labels: {}
options: {}
ipam: {
driver: 'default',
options: {},
config: [
{
subnet: '172.25.0.0/25',
gateway: '172.25.0.1'
}
]
}
})
describe 'internal config -> docker config', ->
it 'should convert an internal representation to a docker representation', ->
network = Network.fromComposeObject('test', 123, {
'driver': 'bridge',
'ipam': {
'driver': 'default',
'config': [
{
'subnet': '172.25.0.0/25',
'gateway': '172.25.0.1'
}
]
}
}, { logger: null, docker: null })
expect(network.toDockerConfig()).to.deep.equal({
Name: '123_test',
Driver: 'bridge',
CheckDuplicate: true,
IPAM: {
Driver: 'default',
Config: [{
Subnet: '172.25.0.0/25'
Gateway: '172.25.0.1'
}]
Options: {}
}
EnableIPv6: false,
Internal: false,
Labels: {
'io.balena.supervised': 'true'
}
})

125
test/18-compose-network.js Normal file
View File

@ -0,0 +1,125 @@
import { expect } from './lib/chai-config';
import { Network } from '../src/compose/network';
describe('compose/network', function() {
describe('compose config -> internal config', function() {
it('should convert a compose configuration to an internal representation', function() {
const network = Network.fromComposeObject(
'test',
123,
{
driver: 'bridge',
ipam: {
driver: 'default',
config: [
{
subnet: '172.25.0.0/25',
gateway: '172.25.0.1',
},
],
},
},
// @ts-ignore ignore passing nulls instead of actual objects
{ logger: null, docker: null },
);
expect(network.config).to.deep.equal({
driver: 'bridge',
ipam: {
driver: 'default',
config: [
{
subnet: '172.25.0.0/25',
gateway: '172.25.0.1',
},
],
options: {},
},
enableIPv6: false,
internal: false,
labels: {},
options: {},
});
});
it('should handle an incomplete ipam configuration', function() {
const network = Network.fromComposeObject(
'test',
123,
{
ipam: {
config: [
{
subnet: '172.25.0.0/25',
gateway: '172.25.0.1',
},
],
},
},
// @ts-ignore ignore passing nulls instead of actual objects
{ logger: null, docker: null },
);
expect(network.config).to.deep.equal({
driver: 'bridge',
enableIPv6: false,
internal: false,
labels: {},
options: {},
ipam: {
driver: 'default',
options: {},
config: [
{
subnet: '172.25.0.0/25',
gateway: '172.25.0.1',
},
],
},
});
});
});
describe('internal config -> docker config', () =>
it('should convert an internal representation to a docker representation', function() {
const network = Network.fromComposeObject(
'test',
123,
{
driver: 'bridge',
ipam: {
driver: 'default',
config: [
{
subnet: '172.25.0.0/25',
gateway: '172.25.0.1',
},
],
},
},
// @ts-ignore ignore passing nulls instead of actual objects
{ logger: null, docker: null },
);
expect(network.toDockerConfig()).to.deep.equal({
Name: '123_test',
Driver: 'bridge',
CheckDuplicate: true,
IPAM: {
Driver: 'default',
Config: [
{
Subnet: '172.25.0.0/25',
Gateway: '172.25.0.1',
},
],
Options: {},
},
EnableIPv6: false,
Internal: false,
Labels: {
'io.balena.supervised': 'true',
},
});
}));
});

View File

@ -1,13 +0,0 @@
{ expect } = require './lib/chai-config'
{ Supervisor } = require '../src/supervisor'
describe 'Startup', ->
it 'should startup correctly', ->
supervisor = new Supervisor()
expect(supervisor.init()).to.not.throw
expect(supervisor.db).to.not.be.null
expect(supervisor.config).to.not.be.null
expect(supervisor.logger).to.not.be.null
expect(supervisor.deviceState).to.not.be.null
expect(supervisor.apiBinder).to.not.be.null

16
test/18-startup.ts Normal file
View File

@ -0,0 +1,16 @@
import { Supervisor } from '../src/supervisor';
import { expect } from './lib/chai-config';
describe('Startup', () => {
it('should startup correctly', function() {
const supervisor = new Supervisor();
expect(supervisor.init()).to.not.throw;
// Cast as any to access private properties
const anySupervisor = supervisor as any;
expect(anySupervisor.db).to.not.be.null;
expect(anySupervisor.config).to.not.be.null;
expect(anySupervisor.logger).to.not.be.null;
expect(anySupervisor.deviceState).to.not.be.null;
expect(anySupervisor.apiBinder).to.not.be.null;
});
});

View File

@ -1,22 +0,0 @@
require('mocha')
{ expect } = require './lib/chai-config'
ComposeUtils = require('../src/compose/utils')
describe 'Composition utilities', ->
it 'Should correctly camel case the configuration', ->
config =
networks: [
'test',
'test2',
]
expect(ComposeUtils.camelCaseConfig(config)).to.deep.equal({
networks: [
'test'
'test2'
]
})

13
test/19-compose-utils.js Normal file
View File

@ -0,0 +1,13 @@
import { expect } from './lib/chai-config';
import * as ComposeUtils from '../src/compose/utils';
describe('Composition utilities', () =>
it('Should correctly camel case the configuration', function() {
const config = {
networks: ['test', 'test2'],
};
expect(ComposeUtils.camelCaseConfig(config)).to.deep.equal({
networks: ['test', 'test2'],
});
}));

View File

@ -4,5 +4,5 @@
"noImplicitAny": false,
"checkJs": true
},
"include": ["src/**/*.ts", "src/**/*.js", "typings/**/*.d.ts"]
"include": ["src/**/*", "test/**/*", "typings/**/*.d.ts"]
}

View File

@ -11,5 +11,5 @@
"resolveJsonModule": true,
"allowJs": true
},
"include": ["src/**/*.ts", "src/**/*.js", "test/**/*.ts", "typings/**/*.d.ts"]
"include": ["src/**/*", "test/**/*", "typings/**/*.d.ts"]
}

View File

@ -6,5 +6,5 @@
"preserveConstEnums": true,
"removeComments": true
},
"include": ["src/**/*.ts", "src/**/*.js", "typings/**/*.d.ts"]
"include": ["src/**/*", "typings/**/*.d.ts"]
}