/* * Copyright 2018 Brigham Young University * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ import {expect} from 'chai'; import { BindContext, DeployContext, PreDeployContext, ServiceContext, ServiceType, UnBindContext, UnDeployContext } from 'handel-extension-api'; import 'mocha'; import * as sinon from 'sinon'; import * as rdsCalls from '../../../src/aws/rds-calls'; import {SharedDbService} from '../../../src/services/shared-db'; import {SharedDbConfig} from '../../../src/services/shared-db/config-types'; import * as db from '../../../src/services/shared-db/db'; import * as migrations from '../../../src/services/shared-db/migrations'; import * as securityGroups from '../../../src/services/shared-db/security-groups'; import accountConfig from '../../fake-account-config'; describe('mysql service deployer', () => { let sandbox: sinon.SinonSandbox; let serviceContext: ServiceContext; let serviceParams: SharedDbConfig; const appName = 'FakeApp'; const envName = 'FakeEnv'; const serviceName = 'FakeService'; const serviceType = 'byu::shared-db'; const mysql = new SharedDbService(); beforeEach(async () => { serviceParams = { type: serviceType, cluster_id: 'shared-db', schema_migrations_dir: './schemas' }; serviceContext = new ServiceContext(appName, envName, serviceName, new ServiceType('byu', 'shared-db'), serviceParams, accountConfig); sandbox = sinon.sandbox.create(); }); afterEach(() => { sandbox.restore(); }); describe('check', () => { it('should disallow schema_migrations_dir and create_ddl_user to be specified together', () => { serviceParams.create_ddl_user = true; const errors = mysql.check(serviceContext, []); expect(errors.length).to.equal(1); expect(errors[0]).to.include(`'schema_migrations_dir' and 'create_ddl_user' fields may not be specified together`); }); it('should return an empty list when all required params are present', () => { const errors = mysql.check(serviceContext, []); expect(errors).to.deep.equal([]); }); }); describe('preDeploy', () => { it('should return the list of RDS security groups in a PreDeployContext', async () => { const getSgsStub = sandbox.stub(securityGroups, 'getSharedRdsSgs').resolves([ { GroupId: 'FakeGroup1' }, { GroupId: 'FakeGroup2' } ]); const preDeployContext = await mysql.preDeploy(serviceContext); expect(getSgsStub.callCount).to.equal(1); expect(preDeployContext).to.be.instanceof(PreDeployContext); expect(preDeployContext.securityGroups.length).to.equal(2); }); }); describe('bind', async () => { it('should bind to one of the shared SGs', async () => { const preDeployContext = new PreDeployContext(serviceContext); const dependentOfServiceContext = new ServiceContext('FakeApp', 'FakeEnv', 'FakeService', new ServiceType('byu', 'kubernetes'), {type: 'kubernetes'}, accountConfig); const dependentOfPreDeployContext = new PreDeployContext(dependentOfServiceContext); const bindSgsStub = sandbox.stub(securityGroups, 'bindToOneOfSharedSGs').resolves(new BindContext(serviceContext, dependentOfServiceContext)); const getDbClusterInfoStub = sandbox.stub(rdsCalls, 'getDBClusterInformation').resolves({ cluster_id: 'FakeClusterId', endpoint: 'some.endpoint.amazon.com', engine: 'aurora-postgresql', port: 5432 }); const bindContext = await mysql.bind(serviceContext, preDeployContext, dependentOfServiceContext, dependentOfPreDeployContext); expect(bindSgsStub.callCount).to.equal(1); expect(getDbClusterInfoStub.callCount).to.equal(1); expect(bindContext).to.be.instanceof(BindContext); }); }); describe('deploy', () => { const writeAddress = 'FakeWriteAddress'; const readAddress = 'FakeReadAddress'; const port = '5432'; const dbName = 'FakeDbName'; const rwSecretName = 'RwSecretName'; const roSecretName = 'RoSecretName'; let ownPreDeployContext: PreDeployContext; let invokeLambdaStub: sinon.SinonStub; let uploadMigrationsStub: sinon.SinonStub; let runMigrationsStub: sinon.SinonStub; beforeEach(() => { ownPreDeployContext = new PreDeployContext(serviceContext); ownPreDeployContext.securityGroups.push({ GroupId: 'FakeId' }); invokeLambdaStub = sandbox.stub(db, 'invokeSharedRdsLambda'); uploadMigrationsStub = sandbox.stub(migrations, 'uploadMigrationsToS3').resolves('FakePath'); runMigrationsStub = sandbox.stub(migrations, 'runSchemaMigrations').resolves(); }); it('should deploy the database', async () => { invokeLambdaStub.resolves({ status: 'success', cluster_id: 'FakeClusterId', db_write_address: writeAddress, db_read_address: readAddress, db_port: 5432, db_name: dbName, secretNames: { rw: rwSecretName, ro: roSecretName } }); const deployContext = await mysql.deploy(serviceContext, ownPreDeployContext, []); expect(invokeLambdaStub.callCount).to.equal(1); expect(uploadMigrationsStub.callCount).to.equal(1); expect(runMigrationsStub.callCount).to.equal(1); expect(deployContext).to.be.instanceof(DeployContext); expect(deployContext.environmentVariables.FAKESERVICE_CLUSTER_ENDPOINT).to.equal(writeAddress); expect(deployContext.environmentVariables.FAKESERVICE_READ_ENDPOINT).to.equal(readAddress); expect(deployContext.environmentVariables.FAKESERVICE_PORT).to.equal(port); expect(deployContext.environmentVariables.FAKESERVICE_DATABASE_NAME).to.equal(dbName); expect(deployContext.environmentVariables.FAKESERVICE_RW_USER_SECRET_NAME).to.equal(rwSecretName); expect(deployContext.environmentVariables.FAKESERVICE_RO_USER_SECRET_NAME).to.equal(roSecretName); }); it('should allow a DDL user to be created (and not run migrations as a result)', async () => { serviceParams.create_ddl_user = true; delete serviceParams.schema_migrations_dir; const ddlSecretName = 'DdlSecretName'; invokeLambdaStub.resolves({ status: 'success', cluster_id: 'FakeClusterId', db_write_address: writeAddress, db_read_address: readAddress, db_port: 5432, db_name: dbName, secretNames: { rw: rwSecretName, ro: roSecretName, ddl: ddlSecretName } }); const deployContext = await mysql.deploy(serviceContext, ownPreDeployContext, []); expect(invokeLambdaStub.callCount).to.equal(1); expect(uploadMigrationsStub.callCount).to.equal(0); expect(runMigrationsStub.callCount).to.equal(0); expect(deployContext).to.be.instanceof(DeployContext); expect(deployContext.environmentVariables.FAKESERVICE_CLUSTER_ENDPOINT).to.equal(writeAddress); expect(deployContext.environmentVariables.FAKESERVICE_READ_ENDPOINT).to.equal(readAddress); expect(deployContext.environmentVariables.FAKESERVICE_PORT).to.equal(port); expect(deployContext.environmentVariables.FAKESERVICE_DATABASE_NAME).to.equal(dbName); expect(deployContext.environmentVariables.FAKESERVICE_RW_USER_SECRET_NAME).to.equal(rwSecretName); expect(deployContext.environmentVariables.FAKESERVICE_RO_USER_SECRET_NAME).to.equal(roSecretName); expect(deployContext.environmentVariables.FAKESERVICE_DDL_USER_SECRET_NAME).to.equal(ddlSecretName); }); it('should throw an error if the lambda deploy didnt work', async () => { const errMsg = 'Fake error message'; invokeLambdaStub.resolves({ status: 'failure', message: errMsg }); try { await mysql.deploy(serviceContext, ownPreDeployContext, []); expect(true).to.equal(false); } catch (err) { expect(err.message).to.include(errMsg); expect(invokeLambdaStub.callCount).to.equal(1); } }); }); describe('unBind', async () => { it('should unbind from one of the shared SGs', async () => { const preDeployContext = new PreDeployContext(serviceContext); const dependentOfServiceContext = new ServiceContext('FakeApp', 'FakeEnv', 'FakeService', new ServiceType('byu', 'kubernetes'), {type: 'kubernetes'}, accountConfig); const dependentOfPreDeployContext = new PreDeployContext(dependentOfServiceContext); const unBindStub = sandbox.stub(securityGroups, 'unBindFromSharedSgs').resolves(new UnBindContext(serviceContext, dependentOfServiceContext)); const getDbClusterInfoStub = sandbox.stub(rdsCalls, 'getDBClusterInformation').resolves({ cluster_id: 'FakeClusterId', endpoint: 'some.endpoint.amazon.com', engine: 'aurora-postgresql', port: 5432 }); const unBindContext = await mysql.unBind(serviceContext, preDeployContext, dependentOfServiceContext, dependentOfPreDeployContext); expect(unBindStub.callCount).to.equal(1); expect(getDbClusterInfoStub.callCount).to.equal(1); expect(unBindContext).to.be.instanceof(UnBindContext); }); }); describe('unDeploy', async () => { it('should run undeploy on the service', async () => { const invokeLambdaStub = sandbox.stub(db, 'invokeSharedRdsLambda').resolves({ status: 'success', message: 'something' }); const deleteMigrationsStub = sandbox.stub(migrations, 'deleteMigrationsFromS3').resolves(); const unDeployContext = await mysql.unDeploy(serviceContext); expect(invokeLambdaStub.callCount).to.equal(1); expect(deleteMigrationsStub.callCount).to.equal(1); expect(unDeployContext).to.be.instanceof(UnDeployContext); }); }); });