mirror of
https://github.com/balena-os/balena-supervisor.git
synced 2024-12-19 13:47:54 +00:00
Fix mock dockerode module
Tests using the mock `testWithData` method were not restoring the dockerode prototype to the default values, making some tests behave differently when run individually that when run with the suite. This commit fixes the `testWithData` method and some malfunctioning v1 API tests because of the change. It doesn't fix all the tests
This commit is contained in:
parent
e2d54e9d6c
commit
c70aedf044
@ -73,6 +73,7 @@ describe('SupervisorAPI [V1 Endpoints]', () => {
|
||||
afterEach(() => {
|
||||
// Clear Dockerode actions recorded for each test
|
||||
mockedDockerode.resetHistory();
|
||||
appMock.unmockAll();
|
||||
});
|
||||
|
||||
before(async () => {
|
||||
@ -120,7 +121,6 @@ describe('SupervisorAPI [V1 Endpoints]', () => {
|
||||
healthCheckStubs.forEach((hc) => hc.restore());
|
||||
// Remove any test data generated
|
||||
await mockedAPI.cleanUp();
|
||||
appMock.unmockAll();
|
||||
targetStateCacheMock.restore();
|
||||
loggerStub.restore();
|
||||
});
|
||||
@ -213,7 +213,6 @@ describe('SupervisorAPI [V1 Endpoints]', () => {
|
||||
|
||||
describe('GET /v1/apps/:appId', () => {
|
||||
it('does not return information for an application when there is more than 1 container', async () => {
|
||||
// Every test case in this suite has a 3 service release mocked so just make the request
|
||||
await request
|
||||
.get('/v1/apps/2')
|
||||
.set('Accept', 'application/json')
|
||||
@ -236,32 +235,39 @@ describe('SupervisorAPI [V1 Endpoints]', () => {
|
||||
});
|
||||
appMock.mockManagers([container], [], []);
|
||||
appMock.mockImages([], false, [image]);
|
||||
// Make request
|
||||
await request
|
||||
.get('/v1/apps/2')
|
||||
.set('Accept', 'application/json')
|
||||
.set('Authorization', `Bearer ${apiKeys.cloudApiKey}`)
|
||||
.expect(sampleResponses.V1.GET['/apps/2'].statusCode)
|
||||
.expect('Content-Type', /json/)
|
||||
.then((response) => {
|
||||
expect(response.body).to.deep.equal(
|
||||
sampleResponses.V1.GET['/apps/2'].body,
|
||||
);
|
||||
});
|
||||
await mockedDockerode.testWithData(
|
||||
{ containers: [container], images: [image] },
|
||||
async () => {
|
||||
// Make request
|
||||
await request
|
||||
.get('/v1/apps/2')
|
||||
.set('Accept', 'application/json')
|
||||
.set('Authorization', `Bearer ${apiKeys.cloudApiKey}`)
|
||||
.expect(sampleResponses.V1.GET['/apps/2'].statusCode)
|
||||
.expect('Content-Type', /json/)
|
||||
.then((response) => {
|
||||
expect(response.body).to.deep.equal(
|
||||
sampleResponses.V1.GET['/apps/2'].body,
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('POST /v1/apps/:appId/stop', () => {
|
||||
it('does not allow stopping an application when there is more than 1 container', async () => {
|
||||
// Every test case in this suite has a 3 service release mocked so just make the request
|
||||
await request
|
||||
.post('/v1/apps/2/stop')
|
||||
.set('Accept', 'application/json')
|
||||
.set('Authorization', `Bearer ${apiKeys.cloudApiKey}`)
|
||||
.expect(
|
||||
sampleResponses.V1.GET['/apps/2/stop [Multiple containers running]']
|
||||
.statusCode,
|
||||
);
|
||||
await mockedDockerode.testWithData({ containers, images }, async () => {
|
||||
await request
|
||||
.post('/v1/apps/2/stop')
|
||||
.set('Accept', 'application/json')
|
||||
.set('Authorization', `Bearer ${apiKeys.cloudApiKey}`)
|
||||
.expect(
|
||||
sampleResponses.V1.GET['/apps/2/stop [Multiple containers running]']
|
||||
.statusCode,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('stops a SPECIFIC application and returns a containerId', async () => {
|
||||
@ -298,11 +304,13 @@ describe('SupervisorAPI [V1 Endpoints]', () => {
|
||||
describe('POST /v1/apps/:appId/start', () => {
|
||||
it('does not allow starting an application when there is more than 1 container', async () => {
|
||||
// Every test case in this suite has a 3 service release mocked so just make the request
|
||||
await request
|
||||
.post('/v1/apps/2/start')
|
||||
.set('Accept', 'application/json')
|
||||
.set('Authorization', `Bearer ${apiKeys.cloudApiKey}`)
|
||||
.expect(400);
|
||||
await mockedDockerode.testWithData({ containers, images }, async () => {
|
||||
await request
|
||||
.post('/v1/apps/2/start')
|
||||
.set('Accept', 'application/json')
|
||||
.set('Authorization', `Bearer ${apiKeys.cloudApiKey}`)
|
||||
.expect(400);
|
||||
});
|
||||
});
|
||||
|
||||
it('starts a SPECIFIC application and returns a containerId', async () => {
|
||||
@ -387,14 +395,19 @@ describe('SupervisorAPI [V1 Endpoints]', () => {
|
||||
appMock.mockManagers([container], [], []);
|
||||
appMock.mockImages([], false, [image]);
|
||||
|
||||
const response = await request
|
||||
.post('/v1/reboot')
|
||||
.set('Accept', 'application/json')
|
||||
.set('Authorization', `Bearer ${apiKeys.cloudApiKey}`)
|
||||
.expect(202);
|
||||
await mockedDockerode.testWithData(
|
||||
{ containers: [container], images: [image] },
|
||||
async () => {
|
||||
const response = await request
|
||||
.post('/v1/reboot')
|
||||
.set('Accept', 'application/json')
|
||||
.set('Authorization', `Bearer ${apiKeys.cloudApiKey}`)
|
||||
.expect(202);
|
||||
|
||||
expect(response.body).to.have.property('Data').that.is.not.empty;
|
||||
expect(rebootMock).to.have.been.calledOnce;
|
||||
expect(response.body).to.have.property('Data').that.is.not.empty;
|
||||
expect(rebootMock).to.have.been.calledOnce;
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it('should return 423 and reject the reboot if no locks are set', async () => {
|
||||
@ -417,15 +430,20 @@ describe('SupervisorAPI [V1 Endpoints]', () => {
|
||||
appMock.mockManagers([container], [], []);
|
||||
appMock.mockImages([], false, [image]);
|
||||
|
||||
const response = await request
|
||||
.post('/v1/reboot')
|
||||
.set('Accept', 'application/json')
|
||||
.set('Authorization', `Bearer ${apiKeys.cloudApiKey}`)
|
||||
.expect(423);
|
||||
await mockedDockerode.testWithData(
|
||||
{ containers: [container], images: [image] },
|
||||
async () => {
|
||||
const response = await request
|
||||
.post('/v1/reboot')
|
||||
.set('Accept', 'application/json')
|
||||
.set('Authorization', `Bearer ${apiKeys.cloudApiKey}`)
|
||||
.expect(423);
|
||||
|
||||
expect(updateLock.lock).to.be.calledOnce;
|
||||
expect(response.body).to.have.property('Error').that.is.not.empty;
|
||||
expect(rebootMock).to.not.have.been.called;
|
||||
expect(updateLock.lock).to.be.calledOnce;
|
||||
expect(response.body).to.have.property('Error').that.is.not.empty;
|
||||
expect(rebootMock).to.not.have.been.called;
|
||||
},
|
||||
);
|
||||
|
||||
(updateLock.lock as SinonStub).restore();
|
||||
});
|
||||
@ -450,16 +468,21 @@ describe('SupervisorAPI [V1 Endpoints]', () => {
|
||||
appMock.mockManagers([container], [], []);
|
||||
appMock.mockImages([], false, [image]);
|
||||
|
||||
const response = await request
|
||||
.post('/v1/reboot')
|
||||
.send({ force: true })
|
||||
.set('Accept', 'application/json')
|
||||
.set('Authorization', `Bearer ${apiKeys.cloudApiKey}`)
|
||||
.expect(202);
|
||||
await mockedDockerode.testWithData(
|
||||
{ containers: [container], images: [image] },
|
||||
async () => {
|
||||
const response = await request
|
||||
.post('/v1/reboot')
|
||||
.send({ force: true })
|
||||
.set('Accept', 'application/json')
|
||||
.set('Authorization', `Bearer ${apiKeys.cloudApiKey}`)
|
||||
.expect(202);
|
||||
|
||||
expect(updateLock.lock).to.be.calledOnce;
|
||||
expect(response.body).to.have.property('Data').that.is.not.empty;
|
||||
expect(rebootMock).to.have.been.calledOnce;
|
||||
expect(updateLock.lock).to.be.calledOnce;
|
||||
expect(response.body).to.have.property('Data').that.is.not.empty;
|
||||
expect(rebootMock).to.have.been.calledOnce;
|
||||
},
|
||||
);
|
||||
|
||||
(updateLock.lock as SinonStub).restore();
|
||||
});
|
||||
@ -488,14 +511,19 @@ describe('SupervisorAPI [V1 Endpoints]', () => {
|
||||
appMock.mockManagers([container], [], []);
|
||||
appMock.mockImages([], false, [image]);
|
||||
|
||||
const response = await request
|
||||
.post('/v1/shutdown')
|
||||
.set('Accept', 'application/json')
|
||||
.set('Authorization', `Bearer ${apiKeys.cloudApiKey}`)
|
||||
.expect(202);
|
||||
await mockedDockerode.testWithData(
|
||||
{ containers: [container], images: [image] },
|
||||
async () => {
|
||||
const response = await request
|
||||
.post('/v1/shutdown')
|
||||
.set('Accept', 'application/json')
|
||||
.set('Authorization', `Bearer ${apiKeys.cloudApiKey}`)
|
||||
.expect(202);
|
||||
|
||||
expect(response.body).to.have.property('Data').that.is.not.empty;
|
||||
expect(shutdownMock).to.have.been.calledOnce;
|
||||
expect(response.body).to.have.property('Data').that.is.not.empty;
|
||||
expect(shutdownMock).to.have.been.calledOnce;
|
||||
},
|
||||
);
|
||||
|
||||
shutdownMock.resetHistory();
|
||||
});
|
||||
@ -520,15 +548,20 @@ describe('SupervisorAPI [V1 Endpoints]', () => {
|
||||
appMock.mockManagers([container], [], []);
|
||||
appMock.mockImages([], false, [image]);
|
||||
|
||||
const response = await request
|
||||
.post('/v1/shutdown')
|
||||
.set('Accept', 'application/json')
|
||||
.set('Authorization', `Bearer ${apiKeys.cloudApiKey}`)
|
||||
.expect(423);
|
||||
await mockedDockerode.testWithData(
|
||||
{ containers: [container], images: [image] },
|
||||
async () => {
|
||||
const response = await request
|
||||
.post('/v1/shutdown')
|
||||
.set('Accept', 'application/json')
|
||||
.set('Authorization', `Bearer ${apiKeys.cloudApiKey}`)
|
||||
.expect(423);
|
||||
|
||||
expect(updateLock.lock).to.be.calledOnce;
|
||||
expect(response.body).to.have.property('Error').that.is.not.empty;
|
||||
expect(shutdownMock).to.not.have.been.called;
|
||||
expect(updateLock.lock).to.be.calledOnce;
|
||||
expect(response.body).to.have.property('Error').that.is.not.empty;
|
||||
expect(shutdownMock).to.not.have.been.called;
|
||||
},
|
||||
);
|
||||
|
||||
(updateLock.lock as SinonStub).restore();
|
||||
});
|
||||
@ -553,16 +586,21 @@ describe('SupervisorAPI [V1 Endpoints]', () => {
|
||||
appMock.mockManagers([container], [], []);
|
||||
appMock.mockImages([], false, [image]);
|
||||
|
||||
const response = await request
|
||||
.post('/v1/shutdown')
|
||||
.send({ force: true })
|
||||
.set('Accept', 'application/json')
|
||||
.set('Authorization', `Bearer ${apiKeys.cloudApiKey}`)
|
||||
.expect(202);
|
||||
await mockedDockerode.testWithData(
|
||||
{ containers: [container], images: [image] },
|
||||
async () => {
|
||||
const response = await request
|
||||
.post('/v1/shutdown')
|
||||
.send({ force: true })
|
||||
.set('Accept', 'application/json')
|
||||
.set('Authorization', `Bearer ${apiKeys.cloudApiKey}`)
|
||||
.expect(202);
|
||||
|
||||
expect(updateLock.lock).to.be.calledOnce;
|
||||
expect(response.body).to.have.property('Data').that.is.not.empty;
|
||||
expect(shutdownMock).to.have.been.calledOnce;
|
||||
expect(updateLock.lock).to.be.calledOnce;
|
||||
expect(response.body).to.have.property('Data').that.is.not.empty;
|
||||
expect(shutdownMock).to.have.been.calledOnce;
|
||||
},
|
||||
);
|
||||
|
||||
(updateLock.lock as SinonStub).restore();
|
||||
});
|
||||
|
@ -361,13 +361,15 @@ describe('SupervisorAPI [V2 Endpoints]', () => {
|
||||
});
|
||||
|
||||
it('should return 404 for an unknown service', async () => {
|
||||
await request
|
||||
.post(`/v2/applications/1658654/start-service?apikey=${appScopedKey}`)
|
||||
.send({ serviceName: 'unknown' })
|
||||
.set('Content-type', 'application/json')
|
||||
.expect(404);
|
||||
await mockedDockerode.testWithData({}, async () => {
|
||||
await request
|
||||
.post(`/v2/applications/1658654/start-service?apikey=${appScopedKey}`)
|
||||
.send({ serviceName: 'unknown' })
|
||||
.set('Content-type', 'application/json')
|
||||
.expect(404);
|
||||
|
||||
expect(applicationManagerSpy).to.not.have.been.called;
|
||||
expect(applicationManagerSpy).to.not.have.been.called;
|
||||
});
|
||||
});
|
||||
|
||||
it('should ignore locks and return 200', async () => {
|
||||
@ -465,12 +467,16 @@ describe('SupervisorAPI [V2 Endpoints]', () => {
|
||||
});
|
||||
|
||||
it('should return 404 for an unknown service', async () => {
|
||||
await request
|
||||
.post(`/v2/applications/1658654/restart-service?apikey=${appScopedKey}`)
|
||||
.send({ serviceName: 'unknown' })
|
||||
.set('Content-type', 'application/json')
|
||||
.expect(404);
|
||||
expect(applicationManagerSpy).to.not.have.been.called;
|
||||
await mockedDockerode.testWithData({}, async () => {
|
||||
await request
|
||||
.post(
|
||||
`/v2/applications/1658654/restart-service?apikey=${appScopedKey}`,
|
||||
)
|
||||
.send({ serviceName: 'unknown' })
|
||||
.set('Content-type', 'application/json')
|
||||
.expect(404);
|
||||
expect(applicationManagerSpy).to.not.have.been.called;
|
||||
});
|
||||
});
|
||||
|
||||
it('should return 423 for a service with update locks', async () => {
|
||||
|
@ -81,6 +81,12 @@ export function registerOverride<
|
||||
overrides[name] = fn;
|
||||
}
|
||||
|
||||
export function restoreOverride<T extends DockerodeFunction>(name: T) {
|
||||
if (overrides.hasOwnProperty(name)) {
|
||||
delete overrides[name];
|
||||
}
|
||||
}
|
||||
|
||||
export interface TestData {
|
||||
networks: Dictionary<any>;
|
||||
images: Dictionary<any>;
|
||||
@ -201,6 +207,24 @@ function createMockedDockerode(data: TestData) {
|
||||
return mockedDockerode;
|
||||
}
|
||||
|
||||
type Prototype = Dictionary<(...args: any[]) => any>;
|
||||
function clonePrototype(prototype: Prototype): Prototype {
|
||||
const clone: Prototype = {};
|
||||
Object.getOwnPropertyNames(prototype).forEach((fn) => {
|
||||
if (fn !== 'constructor' && _.isFunction(prototype[fn])) {
|
||||
clone[fn] = prototype[fn];
|
||||
}
|
||||
});
|
||||
|
||||
return clone;
|
||||
}
|
||||
|
||||
function assignPrototype(target: Prototype, source: Prototype) {
|
||||
Object.keys(source).forEach((fn) => {
|
||||
target[fn] = source[fn];
|
||||
});
|
||||
}
|
||||
|
||||
export async function testWithData(
|
||||
data: Partial<TestData>,
|
||||
test: () => Promise<any>,
|
||||
@ -216,7 +240,7 @@ export async function testWithData(
|
||||
};
|
||||
|
||||
// grab the original prototype...
|
||||
const basePrototype = dockerode.prototype;
|
||||
const basePrototype = clonePrototype(dockerode.prototype);
|
||||
|
||||
// @ts-expect-error setting a RO property
|
||||
dockerode.prototype = createMockedDockerode(mockedData);
|
||||
@ -226,7 +250,6 @@ export async function testWithData(
|
||||
await test();
|
||||
} finally {
|
||||
// reset the original prototype...
|
||||
// @ts-expect-error setting a RO property
|
||||
dockerode.prototype = basePrototype;
|
||||
assignPrototype(dockerode.prototype, basePrototype);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user