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(() => {
|
afterEach(() => {
|
||||||
// Clear Dockerode actions recorded for each test
|
// Clear Dockerode actions recorded for each test
|
||||||
mockedDockerode.resetHistory();
|
mockedDockerode.resetHistory();
|
||||||
|
appMock.unmockAll();
|
||||||
});
|
});
|
||||||
|
|
||||||
before(async () => {
|
before(async () => {
|
||||||
@ -120,7 +121,6 @@ describe('SupervisorAPI [V1 Endpoints]', () => {
|
|||||||
healthCheckStubs.forEach((hc) => hc.restore());
|
healthCheckStubs.forEach((hc) => hc.restore());
|
||||||
// Remove any test data generated
|
// Remove any test data generated
|
||||||
await mockedAPI.cleanUp();
|
await mockedAPI.cleanUp();
|
||||||
appMock.unmockAll();
|
|
||||||
targetStateCacheMock.restore();
|
targetStateCacheMock.restore();
|
||||||
loggerStub.restore();
|
loggerStub.restore();
|
||||||
});
|
});
|
||||||
@ -213,7 +213,6 @@ describe('SupervisorAPI [V1 Endpoints]', () => {
|
|||||||
|
|
||||||
describe('GET /v1/apps/:appId', () => {
|
describe('GET /v1/apps/:appId', () => {
|
||||||
it('does not return information for an application when there is more than 1 container', async () => {
|
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
|
await request
|
||||||
.get('/v1/apps/2')
|
.get('/v1/apps/2')
|
||||||
.set('Accept', 'application/json')
|
.set('Accept', 'application/json')
|
||||||
@ -236,32 +235,39 @@ describe('SupervisorAPI [V1 Endpoints]', () => {
|
|||||||
});
|
});
|
||||||
appMock.mockManagers([container], [], []);
|
appMock.mockManagers([container], [], []);
|
||||||
appMock.mockImages([], false, [image]);
|
appMock.mockImages([], false, [image]);
|
||||||
// Make request
|
await mockedDockerode.testWithData(
|
||||||
await request
|
{ containers: [container], images: [image] },
|
||||||
.get('/v1/apps/2')
|
async () => {
|
||||||
.set('Accept', 'application/json')
|
// Make request
|
||||||
.set('Authorization', `Bearer ${apiKeys.cloudApiKey}`)
|
await request
|
||||||
.expect(sampleResponses.V1.GET['/apps/2'].statusCode)
|
.get('/v1/apps/2')
|
||||||
.expect('Content-Type', /json/)
|
.set('Accept', 'application/json')
|
||||||
.then((response) => {
|
.set('Authorization', `Bearer ${apiKeys.cloudApiKey}`)
|
||||||
expect(response.body).to.deep.equal(
|
.expect(sampleResponses.V1.GET['/apps/2'].statusCode)
|
||||||
sampleResponses.V1.GET['/apps/2'].body,
|
.expect('Content-Type', /json/)
|
||||||
);
|
.then((response) => {
|
||||||
});
|
expect(response.body).to.deep.equal(
|
||||||
|
sampleResponses.V1.GET['/apps/2'].body,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('POST /v1/apps/:appId/stop', () => {
|
describe('POST /v1/apps/:appId/stop', () => {
|
||||||
it('does not allow stopping an application when there is more than 1 container', async () => {
|
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
|
// Every test case in this suite has a 3 service release mocked so just make the request
|
||||||
await request
|
await mockedDockerode.testWithData({ containers, images }, async () => {
|
||||||
.post('/v1/apps/2/stop')
|
await request
|
||||||
.set('Accept', 'application/json')
|
.post('/v1/apps/2/stop')
|
||||||
.set('Authorization', `Bearer ${apiKeys.cloudApiKey}`)
|
.set('Accept', 'application/json')
|
||||||
.expect(
|
.set('Authorization', `Bearer ${apiKeys.cloudApiKey}`)
|
||||||
sampleResponses.V1.GET['/apps/2/stop [Multiple containers running]']
|
.expect(
|
||||||
.statusCode,
|
sampleResponses.V1.GET['/apps/2/stop [Multiple containers running]']
|
||||||
);
|
.statusCode,
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('stops a SPECIFIC application and returns a containerId', async () => {
|
it('stops a SPECIFIC application and returns a containerId', async () => {
|
||||||
@ -298,11 +304,13 @@ describe('SupervisorAPI [V1 Endpoints]', () => {
|
|||||||
describe('POST /v1/apps/:appId/start', () => {
|
describe('POST /v1/apps/:appId/start', () => {
|
||||||
it('does not allow starting an application when there is more than 1 container', async () => {
|
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
|
// Every test case in this suite has a 3 service release mocked so just make the request
|
||||||
await request
|
await mockedDockerode.testWithData({ containers, images }, async () => {
|
||||||
.post('/v1/apps/2/start')
|
await request
|
||||||
.set('Accept', 'application/json')
|
.post('/v1/apps/2/start')
|
||||||
.set('Authorization', `Bearer ${apiKeys.cloudApiKey}`)
|
.set('Accept', 'application/json')
|
||||||
.expect(400);
|
.set('Authorization', `Bearer ${apiKeys.cloudApiKey}`)
|
||||||
|
.expect(400);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('starts a SPECIFIC application and returns a containerId', async () => {
|
it('starts a SPECIFIC application and returns a containerId', async () => {
|
||||||
@ -387,14 +395,19 @@ describe('SupervisorAPI [V1 Endpoints]', () => {
|
|||||||
appMock.mockManagers([container], [], []);
|
appMock.mockManagers([container], [], []);
|
||||||
appMock.mockImages([], false, [image]);
|
appMock.mockImages([], false, [image]);
|
||||||
|
|
||||||
const response = await request
|
await mockedDockerode.testWithData(
|
||||||
.post('/v1/reboot')
|
{ containers: [container], images: [image] },
|
||||||
.set('Accept', 'application/json')
|
async () => {
|
||||||
.set('Authorization', `Bearer ${apiKeys.cloudApiKey}`)
|
const response = await request
|
||||||
.expect(202);
|
.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(response.body).to.have.property('Data').that.is.not.empty;
|
||||||
expect(rebootMock).to.have.been.calledOnce;
|
expect(rebootMock).to.have.been.calledOnce;
|
||||||
|
},
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return 423 and reject the reboot if no locks are set', async () => {
|
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.mockManagers([container], [], []);
|
||||||
appMock.mockImages([], false, [image]);
|
appMock.mockImages([], false, [image]);
|
||||||
|
|
||||||
const response = await request
|
await mockedDockerode.testWithData(
|
||||||
.post('/v1/reboot')
|
{ containers: [container], images: [image] },
|
||||||
.set('Accept', 'application/json')
|
async () => {
|
||||||
.set('Authorization', `Bearer ${apiKeys.cloudApiKey}`)
|
const response = await request
|
||||||
.expect(423);
|
.post('/v1/reboot')
|
||||||
|
.set('Accept', 'application/json')
|
||||||
|
.set('Authorization', `Bearer ${apiKeys.cloudApiKey}`)
|
||||||
|
.expect(423);
|
||||||
|
|
||||||
expect(updateLock.lock).to.be.calledOnce;
|
expect(updateLock.lock).to.be.calledOnce;
|
||||||
expect(response.body).to.have.property('Error').that.is.not.empty;
|
expect(response.body).to.have.property('Error').that.is.not.empty;
|
||||||
expect(rebootMock).to.not.have.been.called;
|
expect(rebootMock).to.not.have.been.called;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
(updateLock.lock as SinonStub).restore();
|
(updateLock.lock as SinonStub).restore();
|
||||||
});
|
});
|
||||||
@ -450,16 +468,21 @@ describe('SupervisorAPI [V1 Endpoints]', () => {
|
|||||||
appMock.mockManagers([container], [], []);
|
appMock.mockManagers([container], [], []);
|
||||||
appMock.mockImages([], false, [image]);
|
appMock.mockImages([], false, [image]);
|
||||||
|
|
||||||
const response = await request
|
await mockedDockerode.testWithData(
|
||||||
.post('/v1/reboot')
|
{ containers: [container], images: [image] },
|
||||||
.send({ force: true })
|
async () => {
|
||||||
.set('Accept', 'application/json')
|
const response = await request
|
||||||
.set('Authorization', `Bearer ${apiKeys.cloudApiKey}`)
|
.post('/v1/reboot')
|
||||||
.expect(202);
|
.send({ force: true })
|
||||||
|
.set('Accept', 'application/json')
|
||||||
|
.set('Authorization', `Bearer ${apiKeys.cloudApiKey}`)
|
||||||
|
.expect(202);
|
||||||
|
|
||||||
expect(updateLock.lock).to.be.calledOnce;
|
expect(updateLock.lock).to.be.calledOnce;
|
||||||
expect(response.body).to.have.property('Data').that.is.not.empty;
|
expect(response.body).to.have.property('Data').that.is.not.empty;
|
||||||
expect(rebootMock).to.have.been.calledOnce;
|
expect(rebootMock).to.have.been.calledOnce;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
(updateLock.lock as SinonStub).restore();
|
(updateLock.lock as SinonStub).restore();
|
||||||
});
|
});
|
||||||
@ -488,14 +511,19 @@ describe('SupervisorAPI [V1 Endpoints]', () => {
|
|||||||
appMock.mockManagers([container], [], []);
|
appMock.mockManagers([container], [], []);
|
||||||
appMock.mockImages([], false, [image]);
|
appMock.mockImages([], false, [image]);
|
||||||
|
|
||||||
const response = await request
|
await mockedDockerode.testWithData(
|
||||||
.post('/v1/shutdown')
|
{ containers: [container], images: [image] },
|
||||||
.set('Accept', 'application/json')
|
async () => {
|
||||||
.set('Authorization', `Bearer ${apiKeys.cloudApiKey}`)
|
const response = await request
|
||||||
.expect(202);
|
.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(response.body).to.have.property('Data').that.is.not.empty;
|
||||||
expect(shutdownMock).to.have.been.calledOnce;
|
expect(shutdownMock).to.have.been.calledOnce;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
shutdownMock.resetHistory();
|
shutdownMock.resetHistory();
|
||||||
});
|
});
|
||||||
@ -520,15 +548,20 @@ describe('SupervisorAPI [V1 Endpoints]', () => {
|
|||||||
appMock.mockManagers([container], [], []);
|
appMock.mockManagers([container], [], []);
|
||||||
appMock.mockImages([], false, [image]);
|
appMock.mockImages([], false, [image]);
|
||||||
|
|
||||||
const response = await request
|
await mockedDockerode.testWithData(
|
||||||
.post('/v1/shutdown')
|
{ containers: [container], images: [image] },
|
||||||
.set('Accept', 'application/json')
|
async () => {
|
||||||
.set('Authorization', `Bearer ${apiKeys.cloudApiKey}`)
|
const response = await request
|
||||||
.expect(423);
|
.post('/v1/shutdown')
|
||||||
|
.set('Accept', 'application/json')
|
||||||
|
.set('Authorization', `Bearer ${apiKeys.cloudApiKey}`)
|
||||||
|
.expect(423);
|
||||||
|
|
||||||
expect(updateLock.lock).to.be.calledOnce;
|
expect(updateLock.lock).to.be.calledOnce;
|
||||||
expect(response.body).to.have.property('Error').that.is.not.empty;
|
expect(response.body).to.have.property('Error').that.is.not.empty;
|
||||||
expect(shutdownMock).to.not.have.been.called;
|
expect(shutdownMock).to.not.have.been.called;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
(updateLock.lock as SinonStub).restore();
|
(updateLock.lock as SinonStub).restore();
|
||||||
});
|
});
|
||||||
@ -553,16 +586,21 @@ describe('SupervisorAPI [V1 Endpoints]', () => {
|
|||||||
appMock.mockManagers([container], [], []);
|
appMock.mockManagers([container], [], []);
|
||||||
appMock.mockImages([], false, [image]);
|
appMock.mockImages([], false, [image]);
|
||||||
|
|
||||||
const response = await request
|
await mockedDockerode.testWithData(
|
||||||
.post('/v1/shutdown')
|
{ containers: [container], images: [image] },
|
||||||
.send({ force: true })
|
async () => {
|
||||||
.set('Accept', 'application/json')
|
const response = await request
|
||||||
.set('Authorization', `Bearer ${apiKeys.cloudApiKey}`)
|
.post('/v1/shutdown')
|
||||||
.expect(202);
|
.send({ force: true })
|
||||||
|
.set('Accept', 'application/json')
|
||||||
|
.set('Authorization', `Bearer ${apiKeys.cloudApiKey}`)
|
||||||
|
.expect(202);
|
||||||
|
|
||||||
expect(updateLock.lock).to.be.calledOnce;
|
expect(updateLock.lock).to.be.calledOnce;
|
||||||
expect(response.body).to.have.property('Data').that.is.not.empty;
|
expect(response.body).to.have.property('Data').that.is.not.empty;
|
||||||
expect(shutdownMock).to.have.been.calledOnce;
|
expect(shutdownMock).to.have.been.calledOnce;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
(updateLock.lock as SinonStub).restore();
|
(updateLock.lock as SinonStub).restore();
|
||||||
});
|
});
|
||||||
|
@ -361,13 +361,15 @@ describe('SupervisorAPI [V2 Endpoints]', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should return 404 for an unknown service', async () => {
|
it('should return 404 for an unknown service', async () => {
|
||||||
await request
|
await mockedDockerode.testWithData({}, async () => {
|
||||||
.post(`/v2/applications/1658654/start-service?apikey=${appScopedKey}`)
|
await request
|
||||||
.send({ serviceName: 'unknown' })
|
.post(`/v2/applications/1658654/start-service?apikey=${appScopedKey}`)
|
||||||
.set('Content-type', 'application/json')
|
.send({ serviceName: 'unknown' })
|
||||||
.expect(404);
|
.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 () => {
|
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 () => {
|
it('should return 404 for an unknown service', async () => {
|
||||||
await request
|
await mockedDockerode.testWithData({}, async () => {
|
||||||
.post(`/v2/applications/1658654/restart-service?apikey=${appScopedKey}`)
|
await request
|
||||||
.send({ serviceName: 'unknown' })
|
.post(
|
||||||
.set('Content-type', 'application/json')
|
`/v2/applications/1658654/restart-service?apikey=${appScopedKey}`,
|
||||||
.expect(404);
|
)
|
||||||
expect(applicationManagerSpy).to.not.have.been.called;
|
.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 () => {
|
it('should return 423 for a service with update locks', async () => {
|
||||||
|
@ -81,6 +81,12 @@ export function registerOverride<
|
|||||||
overrides[name] = fn;
|
overrides[name] = fn;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function restoreOverride<T extends DockerodeFunction>(name: T) {
|
||||||
|
if (overrides.hasOwnProperty(name)) {
|
||||||
|
delete overrides[name];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export interface TestData {
|
export interface TestData {
|
||||||
networks: Dictionary<any>;
|
networks: Dictionary<any>;
|
||||||
images: Dictionary<any>;
|
images: Dictionary<any>;
|
||||||
@ -201,6 +207,24 @@ function createMockedDockerode(data: TestData) {
|
|||||||
return mockedDockerode;
|
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(
|
export async function testWithData(
|
||||||
data: Partial<TestData>,
|
data: Partial<TestData>,
|
||||||
test: () => Promise<any>,
|
test: () => Promise<any>,
|
||||||
@ -216,7 +240,7 @@ export async function testWithData(
|
|||||||
};
|
};
|
||||||
|
|
||||||
// grab the original prototype...
|
// grab the original prototype...
|
||||||
const basePrototype = dockerode.prototype;
|
const basePrototype = clonePrototype(dockerode.prototype);
|
||||||
|
|
||||||
// @ts-expect-error setting a RO property
|
// @ts-expect-error setting a RO property
|
||||||
dockerode.prototype = createMockedDockerode(mockedData);
|
dockerode.prototype = createMockedDockerode(mockedData);
|
||||||
@ -226,7 +250,6 @@ export async function testWithData(
|
|||||||
await test();
|
await test();
|
||||||
} finally {
|
} finally {
|
||||||
// reset the original prototype...
|
// reset the original prototype...
|
||||||
// @ts-expect-error setting a RO property
|
assignPrototype(dockerode.prototype, basePrototype);
|
||||||
dockerode.prototype = basePrototype;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user