diff --git a/test/00-init.spec.ts b/test/00-init.spec.ts index 3a663d37..ddf81ce7 100644 --- a/test/00-init.spec.ts +++ b/test/00-init.spec.ts @@ -6,9 +6,6 @@ process.env.DATABASE_PATH_2 = './test/data/database2.sqlite'; process.env.DATABASE_PATH_3 = './test/data/database3.sqlite'; process.env.LED_FILE = './test/data/led_file'; -import * as dbus from 'dbus'; -import { DBusError, DBusInterface } from 'dbus'; -import { stub } from 'sinon'; import * as fs from 'fs'; // Make sure they are no database files left over from @@ -33,31 +30,7 @@ fs.writeFileSync( fs.readFileSync('./test/data/testconfig.json'), ); -stub(dbus, 'getBus').returns({ - getInterface: ( - _serviceName: string, - _objectPath: string, - _interfaceName: string, - interfaceCb: (err: null | DBusError, iface: DBusInterface) => void, - ) => { - interfaceCb(null, { - Get: ( - _unitName: string, - _property: string, - getCb: (err: null | Error, value: unknown) => void, - ) => { - getCb(null, 'this is the value'); - }, - GetUnit: ( - _unitName: string, - getUnitCb: (err: null | Error, unitPath: string) => void, - ) => { - getUnitCb(null, 'this is the unit path'); - }, - } as any); - }, -} as any); - +import './lib/mocked-dbus'; import './lib/mocked-dockerode'; import './lib/mocked-iptables'; import './lib/mocked-event-tracker'; diff --git a/test/39-compose-images.spec.ts b/test/39-compose-images.spec.ts new file mode 100644 index 00000000..8a63f6c7 --- /dev/null +++ b/test/39-compose-images.spec.ts @@ -0,0 +1,133 @@ +import * as _ from 'lodash'; + +import { docker } from '../src/lib/docker-utils'; +import { expect } from './lib/chai-config'; +import * as Images from '../src/compose/images'; +import * as mockedDockerode from './lib/mocked-dockerode'; +import * as mockedDatabase from './lib/mocked-database'; +import * as db from '../src/db'; +import * as sampleImageData from './data/compose-image-data.json'; + +describe('compose/images', () => { + before(() => { + mockedDatabase.create(); + }); + + after(() => { + try { + mockedDatabase.restore(); + } catch (e) { + /* noop */ + } + }); + + afterEach(() => { + // Clear Dockerode actions recorded for each test + mockedDockerode.resetHistory(); + }); + + it('Removes a legacy Image', async () => { + const images = sampleImageData['legacy-image'].dockerode; + const IMAGE_TO_REMOVE = sampleImageData['legacy-image'].remove; + const IMAGES_FROM_DB = sampleImageData['legacy-image'].database; + // Stub the database to return images we want + mockedDatabase.setImages(IMAGES_FROM_DB).stub(); + // Perform the test with our specially crafted data + await mockedDockerode.testWithData({ images }, async () => { + // Check that our legacy image exists + await expect(docker.getImage(IMAGE_TO_REMOVE.name).inspect()).to + .eventually.not.be.undefined; + await expect( + db.models('image').select().where(IMAGE_TO_REMOVE), + ).to.eventually.have.lengthOf(1); + // Check that docker has this Image + await expect(docker.getImage(IMAGE_TO_REMOVE.name).inspect()).to + .eventually.not.be.undefined; + // Now remove this image... + await Images.remove(IMAGE_TO_REMOVE); + // Check if it still exists! + await expect(docker.getImage(IMAGE_TO_REMOVE.name).inspect()).to + .eventually.be.undefined; + await expect(db.models('image').select().where(IMAGE_TO_REMOVE)).to + .eventually.be.empty; + // Check that docker remove was called once + const removeSteps = _(mockedDockerode.actions) + .pickBy({ name: 'remove' }) + .map() + .value(); + expect(removeSteps).to.have.lengthOf(1); + }); + }); + + it('Removes a single Image', async () => { + const images = sampleImageData['single-image'].dockerode; + const IMAGE_TO_REMOVE = sampleImageData['single-image'].remove; + const IMAGES_FROM_DB = sampleImageData['single-image'].database; + // Stub the database to return images we want + mockedDatabase.setImages(IMAGES_FROM_DB).stub(); + // Perform the test with our specially crafted data + await mockedDockerode.testWithData({ images }, async () => { + // Check that a single image is returned when given entire object + expect( + db.models('image').select().where(IMAGE_TO_REMOVE), + ).to.eventually.have.lengthOf(1); + // Check that only one image with this dockerImageId exists in the db + expect( + db + .models('image') + .where({ dockerImageId: IMAGE_TO_REMOVE.dockerImageId }) + .select(), + ).to.eventually.have.lengthOf(1); + // Now remove this image... + await Images.remove(IMAGE_TO_REMOVE); + // Check that docker does not have this image + await expect(docker.getImage(IMAGE_TO_REMOVE.name).inspect()).to + .eventually.be.empty; + // Check that the database longer has this image + await expect(db.models('image').select().where(IMAGE_TO_REMOVE)).to + .eventually.be.empty; + // Check that docker remove was called once + const removeSteps = _(mockedDockerode.actions) + .pickBy({ name: 'remove' }) + .map() + .value(); + expect(removeSteps).to.have.lengthOf(1); + }); + }); + + it('Removes an Image with digests', async () => { + const images = sampleImageData['image-with-digests'].dockerode; + const IMAGE_TO_REMOVE = sampleImageData['image-with-digests'].remove; + const IMAGES_FROM_DB = sampleImageData['image-with-digests'].database; + // Stub the database to return images we want + mockedDatabase.setImages(IMAGES_FROM_DB).stub(); + // Perform the test with our specially crafted data + await mockedDockerode.testWithData({ images }, async () => { + // Check that a single image is returned when given entire object + expect( + db.models('image').select().where(IMAGE_TO_REMOVE), + ).to.eventually.have.lengthOf(1); + // Check that multiple images with the same dockerImageId are returned + expect( + db + .models('image') + .where({ dockerImageId: IMAGE_TO_REMOVE.dockerImageId }) + .select(), + ).to.eventually.have.lengthOf(2); + // Now remove these image... + await Images.remove(IMAGE_TO_REMOVE); + // Check that docker does not have this image + await expect(docker.getImage(IMAGE_TO_REMOVE.name).inspect()).to + .eventually.be.empty; + // Check that the database no longer has this image + await expect(db.models('image').select().where(IMAGE_TO_REMOVE)).to + .eventually.be.empty; + // Check that docker remove was called twice + const removeSteps = _(mockedDockerode.actions) + .pickBy({ name: 'remove' }) + .map() + .value(); + expect(removeSteps).to.have.lengthOf(2); + }); + }); +}); diff --git a/test/data/compose-image-data.json b/test/data/compose-image-data.json new file mode 100644 index 00000000..41b382d2 --- /dev/null +++ b/test/data/compose-image-data.json @@ -0,0 +1,408 @@ +{ + "legacy-image": { + "remove": { + "id": 246, + "name": "registry2.balena-cloud.com/v2/793f9296017bbfe026334820ab56bb3a@sha256:2c969a1ba1c6bc10df53481f48c6a74dbd562cfb41ba58f81beabd03facf5582", + "appId": 1658654, + "serviceId": 650325, + "serviceName": "app_1", + "imageId": 2693229, + "releaseId": 1524186, + "dependent": 0 + }, + "database": [ + { + "id": 246, + "name": "registry2.balena-cloud.com/v2/793f9296017bbfe026334820ab56bb3a@sha256:2c969a1ba1c6bc10df53481f48c6a74dbd562cfb41ba58f81beabd03facf5582", + "appId": 1658654, + "serviceId": 650325, + "serviceName": "app_1", + "imageId": 2693229, + "releaseId": 1524186, + "dependent": 0 + } + ], + "dockerode": { + "registry2.balena-cloud.com/v2/793f9296017bbfe026334820ab56bb3a@sha256:2c969a1ba1c6bc10df53481f48c6a74dbd562cfb41ba58f81beabd03facf5582": { + "Containers": -1, + "Created": 1599492712, + "Id": "sha256:f1154d76c731f04711e5856b6e6858730e3023d9113124900ac65c2ccc90e8e7", + "Labels": { + "io.balena.architecture": "aarch64", + "io.balena.device-type": "jetson-tx2", + "io.balena.qemu.version": "4.0.0+balena2-aarch64" + }, + "ParentId": "", + "RepoDigests": [ + "registry2.balena-cloud.com/v2/793f9296017bbfe026334820ab56bb3a@sha256:72b80cbd3cc12de08d4adc9dec79916bf466031553f55b59c29e397829ea129f" + ], + "RepoTags": [ + "registry2.balena-cloud.com/v2/793f9296017bbfe026334820ab56bb3a:delta-ada9fbb57d90e61e" + ], + "SharedSize": -1, + "Size": 217024648, + "VirtualSize": 217024648 + }, + "livepush-supervisor:11.12.11": { + "Containers": -1, + "Created": 1599492599, + "Id": "sha256:db5af2c94366275d8e6d7ea3047f2405eab2f04a27f66843634a45958ef59f5a", + "Labels": { + "io.balena.architecture": "aarch64", + "io.balena.livepush-image": "1", + "io.balena.qemu.version": "4.0.0+balena-aarch64" + }, + "ParentId": "sha256:4acb1652178e72c0b6143a08225e0df5ef74b338a0c9e2ca4cd261339f4f0431", + "RepoDigests": null, + "RepoTags": [ + "livepush-supervisor:11.12.11" + ], + "SharedSize": -1, + "Size": 501352885, + "VirtualSize": 501352885 + } + } + }, + "single-image": { + "remove": { + "id": 246, + "name": "registry2.balena-cloud.com/v2/793f9296017bbfe026334820ab56bb3a@sha256:2c969a1ba1c6bc10df53481f48c6a74dbd562cfb41ba58f81beabd03facf5582", + "appId": 1658654, + "serviceId": 650325, + "serviceName": "app_1", + "imageId": 2693229, + "releaseId": 1524186, + "dependent": 0, + "dockerImageId": "sha256:f1154d76c731f04711e5856b6e6858730e3023d9113124900ac65c2ccc90e8e7" + }, + "database": [ + { + "id": 246, + "name": "registry2.balena-cloud.com/v2/793f9296017bbfe026334820ab56bb3a@sha256:2c969a1ba1c6bc10df53481f48c6a74dbd562cfb41ba58f81beabd03facf5582", + "appId": 1658654, + "serviceId": 650325, + "serviceName": "app_1", + "imageId": 2693229, + "releaseId": 1524186, + "dependent": 0, + "dockerImageId": "sha256:f1154d76c731f04711e5856b6e6858730e3023d9113124900ac65c2ccc90e8e7" + }, + { + "id": 247, + "name": "registry2.balena-cloud.com/v2/902cf44eb0ed51675a0bf95a7bbf0c91@sha256:12345a1ba1c6bc10df53481f48c6a74dbd562cfb41ba58f81beabd03facf5582", + "appId": 1658654, + "serviceId": 650331, + "serviceName": "app_2", + "imageId": 2693230, + "releaseId": 1524186, + "dependent": 0, + "dockerImageId": "sha256:f1154d76c731f04711e5856b6e6858730e3023d9113124900ac65c2ccc901234" + } + ], + "dockerode": { + "sha256:acf4069b3cf68d05dc8a2df0e511447927303ebef88f897f05cbad823f240d97": { + "Containers": -1, + "Created": 1599492964, + "Id": "sha256:acf4069b3cf68d05dc8a2df0e511447927303ebef88f897f05cbad823f240d97", + "Labels": { + "io.balena.architecture": "aarch64", + "io.balena.device-type": "jetson-tx2", + "io.balena.qemu.version": "4.0.0+balena2-aarch64" + }, + "ParentId": "", + "RepoDigests": [ + "registry2.balena-cloud.com/v2/772eca412ce780e860b988da6ea26ee0@sha256:7d5e55d6aac00b8504c5c360a3ee59677fc5a7324360f1f54df19d0bb17c2cfe", + "registry2.balena-cloud.com/v2/7f1290fa85b253936ebf6e0dbbd95875@sha256:107f63eb1bc2e9978f2a4bb5b095bc010dd91dd5a6a0a39a494e27ee8b396232" + ], + "RepoTags": [ + "registry2.balena-cloud.com/v2/772eca412ce780e860b988da6ea26ee0:delta-ff3f0a82c404830b", + "registry2.balena-cloud.com/v2/7f1290fa85b253936ebf6e0dbbd95875:delta-79acb6d8bf4795f6" + ], + "SharedSize": -1, + "Size": 217024666, + "VirtualSize": 217024666 + }, + "sha256:f1154d76c731f04711e5856b6e6858730e3023d9113124900ac65c2ccc90e8e7": { + "Containers": -1, + "Created": 1599492712, + "Id": "sha256:f1154d76c731f04711e5856b6e6858730e3023d9113124900ac65c2ccc90e8e7", + "Labels": { + "io.balena.architecture": "aarch64", + "io.balena.device-type": "jetson-tx2", + "io.balena.qemu.version": "4.0.0+balena2-aarch64" + }, + "ParentId": "", + "RepoDigests": [ + "registry2.balena-cloud.com/v2/793f9296017bbfe026334820ab56bb3a@sha256:72b80cbd3cc12de08d4adc9dec79916bf466031553f55b59c29e397829ea129f" + ], + "RepoTags": [ + "registry2.balena-cloud.com/v2/793f9296017bbfe026334820ab56bb3a:delta-ada9fbb57d90e61e" + ], + "SharedSize": -1, + "Size": 217024648, + "VirtualSize": 217024648 + }, + "sha256:db5af2c94366275d8e6d7ea3047f2405eab2f04a27f66843634a45958ef59f5a": { + "Containers": -1, + "Created": 1599492599, + "Id": "sha256:db5af2c94366275d8e6d7ea3047f2405eab2f04a27f66843634a45958ef59f5a", + "Labels": { + "io.balena.architecture": "aarch64", + "io.balena.livepush-image": "1", + "io.balena.qemu.version": "4.0.0+balena-aarch64" + }, + "ParentId": "sha256:4acb1652178e72c0b6143a08225e0df5ef74b338a0c9e2ca4cd261339f4f0431", + "RepoDigests": null, + "RepoTags": [ + "livepush-supervisor:11.12.11" + ], + "SharedSize": -1, + "Size": 501352885, + "VirtualSize": 501352885 + }, + "sha256:272c54e27104cf6c2153538165dba3c29b58ae35837b47134fa55b53ddb61154": { + "Containers": -1, + "Created": 1599181227, + "Id": "sha256:272c54e27104cf6c2153538165dba3c29b58ae35837b47134fa55b53ddb61154", + "Labels": { + "io.balena.architecture": "aarch64", + "io.balena.device-type": "jetson-tx2", + "io.balena.qemu.version": "4.0.0+balena2-aarch64" + }, + "ParentId": "", + "RepoDigests": [ + "registry2.balena-cloud.com/v2/459bffd69101d788cc3e0722a012878a@sha256:3cc9231cf0e117585e53ebfa6bf9f75a9a4eaa371fb82c21ab9bca8fe0d5c3e3", + "registry2.balena-cloud.com/v2/b0cfe2b1e8c5ab3b6da23f0bd92045b4@sha256:ba5f6d1849c63c8d0f11f35fce694464240002d2c3732898935bf0fedf451063" + ], + "RepoTags": [ + "registry2.balena-cloud.com/v2/459bffd69101d788cc3e0722a012878a:delta-80ed841a1d3fefa9", + "registry2.balena-cloud.com/v2/b0cfe2b1e8c5ab3b6da23f0bd92045b4:delta-532f970c60decb81" + ], + "SharedSize": -1, + "Size": 217024558, + "VirtualSize": 217024558 + }, + "sha256:25d6abae14f08de6b80f9d95003e674598738959a535a2f21be34c03675ebd02": { + "Containers": -1, + "Created": 1599146808, + "Id": "sha256:25d6abae14f08de6b80f9d95003e674598738959a535a2f21be34c03675ebd02", + "Labels": { + "io.balena.qemu.version": "4.0.0+balena-aarch64" + }, + "ParentId": "", + "RepoDigests": [ + "balena/aarch64-supervisor@sha256:e7e3b166e855f4c113a67bc528b4ef77408d05e280052f35452f3e2cd7b5322b" + ], + "RepoTags": [ + "balena/aarch64-supervisor:v11.14.0" + ], + "SharedSize": -1, + "Size": 72305850, + "VirtualSize": 72305850 + }, + "sha256:60783b8688d395f9b4ce4b288d941244d1b0a4c43114ba980acd012ccffc6b53": { + "Containers": -1, + "Created": 1585746557, + "Id": "sha256:60783b8688d395f9b4ce4b288d941244d1b0a4c43114ba980acd012ccffc6b53", + "Labels": { + "io.balena.qemu.version": "4.0.0+balena-aarch64" + }, + "ParentId": "", + "RepoDigests": [ + "balenalib/aarch64-alpine-supervisor-base@sha256:6eb712fc797ff68f258d9032cf292c266cb9bd8be4cbdaaafeb5a8824bb104fd" + ], + "RepoTags": [ + "balenalib/aarch64-alpine-supervisor-base:3.11" + ], + "SharedSize": -1, + "Size": 14503741, + "VirtualSize": 14503741 + }, + "sha256:a29f45ccde2ac0bde957b1277b1501f471960c8ca49f1588c6c885941640ae60": { + "Containers": -1, + "Created": 1578015959, + "Id": "sha256:a29f45ccde2ac0bde957b1277b1501f471960c8ca49f1588c6c885941640ae60", + "Labels": null, + "ParentId": "", + "RepoDigests": null, + "RepoTags": [ + "balena-healthcheck-image:latest" + ], + "SharedSize": -1, + "Size": 9136, + "VirtualSize": 9136 + } + } + }, + "image-with-digests": { + "remove": { + "id": 246, + "name": "registry2.balena-cloud.com/v2/793f9296017bbfe026334820ab56bb3a@sha256:2c969a1ba1c6bc10df53481f48c6a74dbd562cfb41ba58f81beabd03facf5582", + "appId": 1658654, + "serviceId": 650325, + "serviceName": "app_1", + "imageId": 2693229, + "releaseId": 1524186, + "dependent": 0, + "dockerImageId": "sha256:f1154d76c731f04711e5856b6e6858730e3023d9113124900ac65c2ccc90e8e7" + }, + "database": [ + { + "id": 246, + "name": "registry2.balena-cloud.com/v2/793f9296017bbfe026334820ab56bb3a@sha256:2c969a1ba1c6bc10df53481f48c6a74dbd562cfb41ba58f81beabd03facf5582", + "appId": 1658654, + "serviceId": 650325, + "serviceName": "app_1", + "imageId": 2693229, + "releaseId": 1524186, + "dependent": 0, + "dockerImageId": "sha256:f1154d76c731f04711e5856b6e6858730e3023d9113124900ac65c2ccc90e8e7" + }, + { + "id": 247, + "name": "registry2.balena-cloud.com/v2/902cf44eb0ed51675a0bf95a7bbf0c91@sha256:2c969a1ba1c6bc10df53481f48c6a74dbd562cfb41ba58f81beabd03facf5582", + "appId": 1658654, + "serviceId": 650331, + "serviceName": "app_2", + "imageId": 2693230, + "releaseId": 1524186, + "dependent": 0, + "dockerImageId": "sha256:f1154d76c731f04711e5856b6e6858730e3023d9113124900ac65c2ccc90e8e7" + } + ], + "dockerode": { + "sha256:acf4069b3cf68d05dc8a2df0e511447927303ebef88f897f05cbad823f240d97": { + "Containers": -1, + "Created": 1599492964, + "Id": "sha256:acf4069b3cf68d05dc8a2df0e511447927303ebef88f897f05cbad823f240d97", + "Labels": { + "io.balena.architecture": "aarch64", + "io.balena.device-type": "jetson-tx2", + "io.balena.qemu.version": "4.0.0+balena2-aarch64" + }, + "ParentId": "", + "RepoDigests": [ + "registry2.balena-cloud.com/v2/772eca412ce780e860b988da6ea26ee0@sha256:7d5e55d6aac00b8504c5c360a3ee59677fc5a7324360f1f54df19d0bb17c2cfe", + "registry2.balena-cloud.com/v2/7f1290fa85b253936ebf6e0dbbd95875@sha256:107f63eb1bc2e9978f2a4bb5b095bc010dd91dd5a6a0a39a494e27ee8b396232" + ], + "RepoTags": [ + "registry2.balena-cloud.com/v2/772eca412ce780e860b988da6ea26ee0:delta-ff3f0a82c404830b", + "registry2.balena-cloud.com/v2/7f1290fa85b253936ebf6e0dbbd95875:delta-79acb6d8bf4795f6" + ], + "SharedSize": -1, + "Size": 217024666, + "VirtualSize": 217024666 + }, + "sha256:f1154d76c731f04711e5856b6e6858730e3023d9113124900ac65c2ccc90e8e7": { + "Containers": -1, + "Created": 1599492712, + "Id": "sha256:f1154d76c731f04711e5856b6e6858730e3023d9113124900ac65c2ccc90e8e7", + "Labels": { + "io.balena.architecture": "aarch64", + "io.balena.device-type": "jetson-tx2", + "io.balena.qemu.version": "4.0.0+balena2-aarch64" + }, + "ParentId": "", + "RepoDigests": [ + "registry2.balena-cloud.com/v2/793f9296017bbfe026334820ab56bb3a@sha256:72b80cbd3cc12de08d4adc9dec79916bf466031553f55b59c29e397829ea129f" + ], + "RepoTags": [ + "registry2.balena-cloud.com/v2/793f9296017bbfe026334820ab56bb3a:delta-ada9fbb57d90e61e" + ], + "SharedSize": -1, + "Size": 217024648, + "VirtualSize": 217024648 + }, + "sha256:db5af2c94366275d8e6d7ea3047f2405eab2f04a27f66843634a45958ef59f5a": { + "Containers": -1, + "Created": 1599492599, + "Id": "sha256:db5af2c94366275d8e6d7ea3047f2405eab2f04a27f66843634a45958ef59f5a", + "Labels": { + "io.balena.architecture": "aarch64", + "io.balena.livepush-image": "1", + "io.balena.qemu.version": "4.0.0+balena-aarch64" + }, + "ParentId": "sha256:4acb1652178e72c0b6143a08225e0df5ef74b338a0c9e2ca4cd261339f4f0431", + "RepoDigests": null, + "RepoTags": [ + "livepush-supervisor:11.12.11" + ], + "SharedSize": -1, + "Size": 501352885, + "VirtualSize": 501352885 + }, + "sha256:272c54e27104cf6c2153538165dba3c29b58ae35837b47134fa55b53ddb61154": { + "Containers": -1, + "Created": 1599181227, + "Id": "sha256:272c54e27104cf6c2153538165dba3c29b58ae35837b47134fa55b53ddb61154", + "Labels": { + "io.balena.architecture": "aarch64", + "io.balena.device-type": "jetson-tx2", + "io.balena.qemu.version": "4.0.0+balena2-aarch64" + }, + "ParentId": "", + "RepoDigests": [ + "registry2.balena-cloud.com/v2/459bffd69101d788cc3e0722a012878a@sha256:3cc9231cf0e117585e53ebfa6bf9f75a9a4eaa371fb82c21ab9bca8fe0d5c3e3", + "registry2.balena-cloud.com/v2/b0cfe2b1e8c5ab3b6da23f0bd92045b4@sha256:ba5f6d1849c63c8d0f11f35fce694464240002d2c3732898935bf0fedf451063" + ], + "RepoTags": [ + "registry2.balena-cloud.com/v2/459bffd69101d788cc3e0722a012878a:delta-80ed841a1d3fefa9", + "registry2.balena-cloud.com/v2/b0cfe2b1e8c5ab3b6da23f0bd92045b4:delta-532f970c60decb81" + ], + "SharedSize": -1, + "Size": 217024558, + "VirtualSize": 217024558 + }, + "sha256:25d6abae14f08de6b80f9d95003e674598738959a535a2f21be34c03675ebd02": { + "Containers": -1, + "Created": 1599146808, + "Id": "sha256:25d6abae14f08de6b80f9d95003e674598738959a535a2f21be34c03675ebd02", + "Labels": { + "io.balena.qemu.version": "4.0.0+balena-aarch64" + }, + "ParentId": "", + "RepoDigests": [ + "balena/aarch64-supervisor@sha256:e7e3b166e855f4c113a67bc528b4ef77408d05e280052f35452f3e2cd7b5322b" + ], + "RepoTags": [ + "balena/aarch64-supervisor:v11.14.0" + ], + "SharedSize": -1, + "Size": 72305850, + "VirtualSize": 72305850 + }, + "sha256:60783b8688d395f9b4ce4b288d941244d1b0a4c43114ba980acd012ccffc6b53": { + "Containers": -1, + "Created": 1585746557, + "Id": "sha256:60783b8688d395f9b4ce4b288d941244d1b0a4c43114ba980acd012ccffc6b53", + "Labels": { + "io.balena.qemu.version": "4.0.0+balena-aarch64" + }, + "ParentId": "", + "RepoDigests": [ + "balenalib/aarch64-alpine-supervisor-base@sha256:6eb712fc797ff68f258d9032cf292c266cb9bd8be4cbdaaafeb5a8824bb104fd" + ], + "RepoTags": [ + "balenalib/aarch64-alpine-supervisor-base:3.11" + ], + "SharedSize": -1, + "Size": 14503741, + "VirtualSize": 14503741 + }, + "sha256:a29f45ccde2ac0bde957b1277b1501f471960c8ca49f1588c6c885941640ae60": { + "Containers": -1, + "Created": 1578015959, + "Id": "sha256:a29f45ccde2ac0bde957b1277b1501f471960c8ca49f1588c6c885941640ae60", + "Labels": null, + "ParentId": "", + "RepoDigests": null, + "RepoTags": [ + "balena-healthcheck-image:latest" + ], + "SharedSize": -1, + "Size": 9136, + "VirtualSize": 9136 + } + } + } +} \ No newline at end of file diff --git a/test/lib/mocked-database.ts b/test/lib/mocked-database.ts new file mode 100644 index 00000000..38fd59be --- /dev/null +++ b/test/lib/mocked-database.ts @@ -0,0 +1,68 @@ +import * as _ from 'lodash'; +import { SinonStub, stub } from 'sinon'; +import { QueryBuilder } from 'knex'; + +import * as db from '../../src/db'; +import { Image } from '../../src/compose/images'; + +let modelStub: SinonStub | null = null; +// MOCKED MODELS +const MOCKED_MODELS: Dictionary = {}; +// MOCKED MODEL TYPES +let MOCKED_IMAGES: Image[] = []; + +export function create(): SinonStub { + if (modelStub === null) { + modelStub = stub(db, 'models'); + // Stub model requests to return our stubbed models + modelStub.callsFake((model: string) => { + return MOCKED_MODELS[model]; + }); + } + return modelStub; +} + +export function restore(): void { + if (modelStub !== null) { + modelStub.restore(); + } +} + +export function setImages(images: Image[]) { + MOCKED_IMAGES = images; + return { + stub: stubImages, + }; +} + +function stubImages() { + // Set the functions for this model (add them as you need for your test cases) + MOCKED_MODELS['image'] = ({ + select: () => { + return { + where: async (condition: Partial) => + _(MOCKED_IMAGES).pickBy(condition).map().value(), + }; + }, + where: (condition: Partial) => { + return { + select: async () => _(MOCKED_IMAGES).pickBy(condition).map().value(), + }; + }, + del: () => { + return { + where: async (condition: Partial) => { + return _(MOCKED_IMAGES) + .pickBy(condition) + .map() + .value() + .forEach((val) => { + MOCKED_IMAGES = _.reject(MOCKED_IMAGES, { + id: val.id, + }); + }); + }, + }; + }, + } as unknown) as QueryBuilder; +} diff --git a/test/lib/mocked-dbus.ts b/test/lib/mocked-dbus.ts new file mode 100644 index 00000000..214b07a4 --- /dev/null +++ b/test/lib/mocked-dbus.ts @@ -0,0 +1,28 @@ +import * as dbus from 'dbus'; +import { DBusError, DBusInterface } from 'dbus'; +import { stub } from 'sinon'; + +stub(dbus, 'getBus').returns({ + getInterface: ( + _serviceName: string, + _objectPath: string, + _interfaceName: string, + interfaceCb: (err: null | DBusError, iface: DBusInterface) => void, + ) => { + interfaceCb(null, { + Get: ( + _unitName: string, + _property: string, + getCb: (err: null | Error, value: unknown) => void, + ) => { + getCb(null, 'this is the value'); + }, + GetUnit: ( + _unitName: string, + getUnitCb: (err: null | Error, unitPath: string) => void, + ) => { + getUnitCb(null, 'this is the unit path'); + }, + } as any); + }, +} as any); diff --git a/test/lib/mocked-dockerode.ts b/test/lib/mocked-dockerode.ts index 835ac4f5..50471510 100644 --- a/test/lib/mocked-dockerode.ts +++ b/test/lib/mocked-dockerode.ts @@ -15,6 +15,24 @@ export class NotFoundError extends TypedError { const overrides: Dictionary<(...args: any[]) => Resolvable> = {}; +interface Action { + name: string; + parameters: Dictionary; +} + +export let actions: Action[] = []; + +export function resetHistory() { + actions = []; +} + +function addAction(name: string, parameters: Dictionary) { + actions.push({ + name, + parameters, + }); +} + type DockerodeFunction = keyof dockerode; for (const fn of Object.getOwnPropertyNames(dockerode.prototype)) { if ( @@ -62,18 +80,28 @@ export interface TestData { function createMockedDockerode(data: TestData) { const mockedDockerode = dockerode.prototype; mockedDockerode.getNetwork = (id: string) => { + addAction('getNetwork', { id }); return { inspect: async () => { + addAction('inspect', {}); return data.networks[id]; }, } as dockerode.Network; }; mockedDockerode.getImage = (name: string) => { + addAction('getImage', { name }); return { inspect: async () => { + addAction('inspect', {}); return data.images[name]; }, + remove: async () => { + addAction('remove', {}); + data.images = _.reject(data.images, { + name, + }); + }, } as dockerode.Image; };