Some helpers for CFWheels DBMigrate Plugin

Here is a handy controller that I use with my CFWheels application deployments & for debugging my database migrations with Troy Murray’s invaluable CFWheels DBMigrate Plugin.

I use a git branching strategy that utilises feature branches, which can sometimes lead to database migration files being out of sequence based on their creation date.. So I needed a way to test that all current migration are in the correct order… Enter the magical DBMigrate.cfc controller.

To get this up and running, You’ll need to install the CFWheels DBMigrate Plugin, the /models/DBMigrateVersion.cfc model file and the /controllers/DBMigrate.cfc controller from https://github.com/chapmandu/cfwheels-dbmigrate-plugin-tools.

Once these files are in place, you can call the following URLs and see the JSON they return:

http://yoursite/index.cfm?controller=dbmigrate&action=ping

{
db: "okay"
}

http://yoursite/index.cfm?controller=dbmigrate&action=migrations

{
totalMigrationCount: 4,
isOrdered: true,
latest: "20160205201245",
versions: [
{
version: 20140527111730,
cfc: "20140527111730_create_tables",
migrated: true,
details: "create tables"
},
{
version: 20160205201146,
cfc: "20160205201146_insert_lookup_rows",
migrated: false,
details: "insert lookup rows"
},
{
version: 20160205201233,
cfc: "20160205201233_add_plain_text_password_field_to_users_table",
migrated: false,
details: "add plain text password field to users table"
},
{
version: 20160205201245,
cfc: "20160205201245_create_credit_card_number_columns",
migrated: false,
details: "create credit card number columns"
}
],
current: 20140527111730,
migratedCount: 1,
notMigratedCount: 3,
isMigrated: false
}

http://yoursite/index.cfm?controller=dbmigrate&action=current

{
current: 20140527111730
}

http://yoursite/index.cfm?controller=dbmigrate&action=latest

{
latest: 20160205201245
}

http://yoursite/index.cfm?controller=dbmigrate&action=ismigrated

{
ismigrated: false
}

The main one I use for my automated tests is the big sexy action=migrations packet. If any of the migrations are not in the correct sequence, the isordered node in the JSON will have a value of false Here is the RocketUnit test that I run to check if all migration are in the right order (and can be run automatically).

public void function test_db_checks_awaiting_migration_files_should_be_in_order()
{
    loc.args.action = "migrations";
    loc.response = getResponse(argumentCollection=loc.args);
    loc.substring = '"isordered":true'
    assert('loc.response contains loc.substring');
}

Disclaimer: Please be careful leaving this controller lying around in your production environment.. It doesn’t perform any database changes, but can give some basic information about your database migrations to anyone who may stumble upon your URL..

If you use the DBMigrate plugin, you may like this blog post… If you don’t use the plugin.. You really should.

Advertisements

Ensuring Full Unit Test Coverage In Your CFWheels App

Ensuring that you have unit tested all (or most) of your code in your CFWheels app is an important part of any test suite. As you hopefully know by now, the CFWheels framework features an in-built unit test framework.

The code below is a test itself, that will scan your controllers, models and views folders and make sure that you have a corresponding “test” cfc for each. If it discovers that a test is missing, it will fail.. so your app will not pass unless it has full test coverage.

To get it working, just copy the code below and save it into a file in /tests/TestsExist.cfc (or anywhere under the tests folder actually). You can retro-fit it to any app, though you may need to re-organise your test files somewhat.

There are some conventions that you need to follow to have it work properly, but I consider this being well organised (and you should be used to conventions by now). Your folder structure under /myapp/tests/ should look like this... Say you have a Login.cfc controller, your controller test cfcs themselves should be named like this TestLogin.cfc.. but you could also have multiple test files for your Login.cfc, just as long as the file BEGINS with “TestLogin”, eg: TestLoginRedirects.cfc, TestLoginBusinessLogic.cfc.. you get the idea.

The same goes for models and views. There is much one could do to improve/re-factor/customise this code, but it will hopefully catch anything you may have forgotten to test. Feel free to contribute to this project on GitHub.

So to summarise:

  • Re-organise your folders as per the example above
  • Re-name your test cfc files using Test{YourControllerName}.cfc
  • Copy the code into a TestsExist.cfc file under the /tests/ folder
  • Run your unit tests
<cfcomponent extends="wheelsMapping.Test">
	
	<cffunction name="setup">
		<cfset testsPath = ExpandPath("tests")>
		<cfset controllersPath = ExpandPath("controllers")>
		<cfset modelsPath = ExpandPath("models")>
	</cffunction>
	
	<cffunction name="test_00_setup_and_teardown">
		<cfset assert("true")>
	</cffunction>

	<cffunction name="test_01_all_controller_tests_exist">
		<cfdirectory action="list" directory="#controllersPath#" filter="*.cfc" name="controllers" />
		<cfdirectory action="list" directory="#testsPath & '/' & 'controllers'#" filter="*.cfc" name="controllerTests" />
		<cfset assert("controllerTests.recordCount gt 0")>
		<cfloop query="controllers">
			<cfif ListFindNoCase("Controller.cfc,Wheels.cfc", controllers.name) eq 0>
				<cfset abbrev = ListFirst(controllers.name, ".")>
				<cfset isTestFileFound = false>
				<cfloop query="controllerTests">
					<cfif FindNoCase("Test" & abbrev, controllerTests.name) gt 0>
						<cfset isTestFileFound = true>
						<cfbreak>
					</cfif>
				</cfloop>
				<cfset assert("isTestFileFound", "controllers.name")>
			</cfif>
		</cfloop>
	</cffunction>
	
	<cffunction name="test_02_all_model_tests_exist">
		<cfdirectory action="list" directory="#modelsPath#" filter="*.cfc" name="models" />
		<cfdirectory action="list" directory="#testsPath & '/' & 'models'#" filter="*.cfc" name="modelTests" />
		<cfset assert("modelTests.recordCount gt 0")>
		<cfloop query="models">
			<cfif ListFindNoCase("Model.cfc,Wheels.cfc", models.name) eq 0>
				<cfset abbrev = ListFirst(models.name, ".")>
				<cfset isTestFileFound = false>
				<cfloop query="modelTests">
					<cfif FindNoCase("Test" & abbrev, modelTests.name) gt 0>
						<cfset isTestFileFound = true>
						<cfbreak>
					</cfif>
				</cfloop>
				<cfset assert("isTestFileFound", "models.name")>
			</cfif>
		</cfloop>
	</cffunction>

	<cffunction name="test_03_all_view_tests_exist">
		<cfdirectory action="list" directory="#controllersPath#" filter="*.cfc" name="controllers" />
		<cfdirectory action="list" directory="#testsPath & '/' & 'views'#" filter="*.cfm" name="viewTests" />
		<cfset assert("viewTests.recordCount gt 0")>
		<cfloop query="controllers">
			<cfif ListFindNoCase("Controller.cfc,Wheels.cfc", controllers.name) eq 0>
				<cfset abbrev = ListFirst(controllers.name, ".")>
				<cfset isTestFileFound = false>
				<cfloop query="viewTests">
					<cfif FindNoCase("Test" & abbrev, viewTests.name) gt 0>
						<cfset isTestFileFound = true>
						<cfbreak>
					</cfif>
				</cfloop>
				<cfset assert("isTestFileFound", "controllers.name")>
			</cfif>
		</cfloop>
	</cffunction>

	<cffunction name="teardown">
	</cffunction>

</cfcomponent>