mirror of
https://github.com/balena-os/balena-supervisor.git
synced 2024-12-26 08:51:06 +00:00
236 lines
5.8 KiB
TypeScript
236 lines
5.8 KiB
TypeScript
|
import { SinonStub, stub, spy, SinonSpy } from 'sinon';
|
||
|
import { Promise } from 'bluebird';
|
||
|
|
||
|
import * as _ from 'lodash';
|
||
|
|
||
|
import { expect } from './lib/chai-config';
|
||
|
import * as TargetState from '../src/device-state/target-state';
|
||
|
import * as request from '../src/lib/request';
|
||
|
|
||
|
const stateEndpointBody = {
|
||
|
local: {
|
||
|
name: 'solitary-bush',
|
||
|
config: {},
|
||
|
apps: {
|
||
|
'123': {
|
||
|
name: 'my-app',
|
||
|
services: {},
|
||
|
volumes: {},
|
||
|
networks: {},
|
||
|
},
|
||
|
},
|
||
|
},
|
||
|
dependent: {
|
||
|
apps: {},
|
||
|
devices: {},
|
||
|
},
|
||
|
};
|
||
|
|
||
|
describe('Target state', () => {
|
||
|
describe('update', () => {
|
||
|
it('should emit target state when a new one is available', async () => {
|
||
|
const req = {
|
||
|
getAsync: () =>
|
||
|
Promise.resolve([
|
||
|
{
|
||
|
statusCode: 200,
|
||
|
headers: {},
|
||
|
} as any,
|
||
|
JSON.stringify(stateEndpointBody),
|
||
|
]),
|
||
|
};
|
||
|
|
||
|
spy(req, 'getAsync');
|
||
|
stub(request, 'getRequestInstance').resolves(req as any);
|
||
|
|
||
|
// Setup target state listener
|
||
|
const listener = stub();
|
||
|
TargetState.emitter.on('target-state-update', listener);
|
||
|
|
||
|
// Perform target state request
|
||
|
await TargetState.update();
|
||
|
|
||
|
expect(request.getRequestInstance).to.be.calledOnce;
|
||
|
|
||
|
// The getAsync method gets called once and includes authorization headers
|
||
|
expect(req.getAsync).to.be.calledOnce;
|
||
|
expect(
|
||
|
_.has((req.getAsync as SinonSpy).args[0], ['headers', 'Authorization']),
|
||
|
);
|
||
|
|
||
|
// The listener should receive the endpoint
|
||
|
expect(listener).to.be.calledOnceWith(JSON.stringify(stateEndpointBody));
|
||
|
|
||
|
// Remove getRequestInstance stub
|
||
|
(request.getRequestInstance as SinonStub).restore();
|
||
|
|
||
|
// new request returns 304
|
||
|
const newReq = {
|
||
|
getAsync: () =>
|
||
|
Promise.resolve([
|
||
|
{
|
||
|
statusCode: 304,
|
||
|
headers: {},
|
||
|
} as any,
|
||
|
]),
|
||
|
};
|
||
|
|
||
|
spy(newReq, 'getAsync');
|
||
|
stub(request, 'getRequestInstance').resolves(newReq as any);
|
||
|
|
||
|
// Perform new target state request
|
||
|
await TargetState.update();
|
||
|
|
||
|
// The new req should have been called
|
||
|
expect(newReq.getAsync).to.be.calledOnce;
|
||
|
|
||
|
// No new calls to the listener
|
||
|
expect(listener).to.be.calledOnce;
|
||
|
|
||
|
// Cleanup
|
||
|
TargetState.emitter.off('target-state-update', listener);
|
||
|
(request.getRequestInstance as SinonStub).restore();
|
||
|
});
|
||
|
|
||
|
it('should emit cached target state if there was no listener for the cached state', async () => {
|
||
|
const req = {
|
||
|
getAsync: () =>
|
||
|
Promise.resolve([
|
||
|
{
|
||
|
statusCode: 200,
|
||
|
headers: {},
|
||
|
} as any,
|
||
|
JSON.stringify(stateEndpointBody),
|
||
|
]),
|
||
|
};
|
||
|
|
||
|
spy(req, 'getAsync');
|
||
|
stub(request, 'getRequestInstance').resolves(req as any);
|
||
|
|
||
|
// Perform target state request
|
||
|
await TargetState.update();
|
||
|
|
||
|
expect(request.getRequestInstance).to.be.calledOnce;
|
||
|
|
||
|
// The getAsync method gets called once and includes authorization headers
|
||
|
expect(req.getAsync).to.be.calledOnce;
|
||
|
expect(
|
||
|
_.has((req.getAsync as SinonSpy).args[0], ['headers', 'Authorization']),
|
||
|
);
|
||
|
|
||
|
// Remove getRequestInstance stub
|
||
|
(request.getRequestInstance as SinonStub).restore();
|
||
|
|
||
|
// new request returns 304
|
||
|
const newReq = {
|
||
|
getAsync: () =>
|
||
|
Promise.resolve([
|
||
|
{
|
||
|
statusCode: 304,
|
||
|
headers: {},
|
||
|
} as any,
|
||
|
]),
|
||
|
};
|
||
|
|
||
|
spy(newReq, 'getAsync');
|
||
|
stub(request, 'getRequestInstance').resolves(newReq as any);
|
||
|
|
||
|
// Setup target state listener after the first request
|
||
|
const listener = stub();
|
||
|
TargetState.emitter.on('target-state-update', listener);
|
||
|
|
||
|
// Perform new target state request
|
||
|
await TargetState.update();
|
||
|
|
||
|
// The new req should have been called
|
||
|
expect(newReq.getAsync).to.be.calledOnce;
|
||
|
|
||
|
// The listener should receive the endpoint
|
||
|
expect(listener).to.be.calledOnceWith(JSON.stringify(stateEndpointBody));
|
||
|
|
||
|
// Cleanup
|
||
|
TargetState.emitter.off('target-state-update', listener);
|
||
|
(request.getRequestInstance as SinonStub).restore();
|
||
|
});
|
||
|
});
|
||
|
|
||
|
describe('get', () => {
|
||
|
it('returns the latest target state endpoint response', async () => {
|
||
|
const req = {
|
||
|
getAsync: () =>
|
||
|
Promise.resolve([
|
||
|
{
|
||
|
statusCode: 200,
|
||
|
headers: {},
|
||
|
} as any,
|
||
|
JSON.stringify(stateEndpointBody),
|
||
|
]),
|
||
|
};
|
||
|
|
||
|
// Setup spies and stubs
|
||
|
spy(req, 'getAsync');
|
||
|
stub(request, 'getRequestInstance').resolves(req as any);
|
||
|
|
||
|
// Perform target state request
|
||
|
const response = await TargetState.get();
|
||
|
|
||
|
// The stubbed methods should only be called once
|
||
|
expect(request.getRequestInstance).to.be.calledOnce;
|
||
|
expect(req.getAsync).to.be.calledOnce;
|
||
|
|
||
|
// Cached value should reflect latest response
|
||
|
expect(response).to.be.equal(JSON.stringify(stateEndpointBody));
|
||
|
|
||
|
// Cleanup
|
||
|
(request.getRequestInstance as SinonStub).restore();
|
||
|
});
|
||
|
|
||
|
it('returns the last cached target state', async () => {
|
||
|
const req = {
|
||
|
getAsync: () =>
|
||
|
Promise.resolve([
|
||
|
{
|
||
|
statusCode: 200,
|
||
|
headers: {},
|
||
|
} as any,
|
||
|
JSON.stringify(stateEndpointBody),
|
||
|
]),
|
||
|
};
|
||
|
|
||
|
// Setup spies and stubs
|
||
|
stub(request, 'getRequestInstance').resolves(req as any);
|
||
|
|
||
|
// Perform target state request, this should
|
||
|
// put the query result in the cache
|
||
|
await TargetState.update();
|
||
|
|
||
|
// Reset the stub
|
||
|
(request.getRequestInstance as SinonStub).restore();
|
||
|
const newReq = {
|
||
|
getAsync: () =>
|
||
|
Promise.resolve([
|
||
|
{
|
||
|
statusCode: 304,
|
||
|
headers: {},
|
||
|
} as any,
|
||
|
]),
|
||
|
};
|
||
|
spy(newReq, 'getAsync');
|
||
|
stub(request, 'getRequestInstance').resolves(newReq as any);
|
||
|
|
||
|
// Perform target state request
|
||
|
const response = await TargetState.get();
|
||
|
|
||
|
// The stubbed methods should only be called once
|
||
|
expect(request.getRequestInstance).to.be.calledOnce;
|
||
|
expect(newReq.getAsync).to.be.calledOnce;
|
||
|
|
||
|
// Cached value should reflect latest response
|
||
|
expect(response).to.be.equal(JSON.stringify(stateEndpointBody));
|
||
|
|
||
|
// Cleanup
|
||
|
(request.getRequestInstance as SinonStub).restore();
|
||
|
});
|
||
|
});
|
||
|
});
|