Skip to content

Commit b51d5c1

Browse files
authored
fix(migrations): improve error handling for loading migrations (#52)
1 parent 0bed42a commit b51d5c1

File tree

6 files changed

+34
-62
lines changed

6 files changed

+34
-62
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,4 @@ typedoc/
55
.vscode/
66
coverage/
77
.nyc_output/
8-
migrations/
8+
./migrations/

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@
3636
"yargs": "^5.0.0"
3737
},
3838
"devDependencies": {
39+
"chai": "^4.1.2",
40+
"chai-as-promised": "^7.1.1",
3941
"cz-conventional-changelog": "^2.0.0",
4042
"del": "^2.2.2",
4143
"husky": "^0.14.3",

src/migration.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@ import * as fs from 'mz/fs';
77

88
export type TaskType = 'up' | 'down';
99

10+
export class MigrationLoadError extends Error {
11+
constructor(public migration: Migration, public migrationDir: string, public error: any) {
12+
super('Error loading migration file ' + migrationDir + sep + chalk.bold(migration.name) + '.js:\n' + error);
13+
}
14+
}
15+
1016
export class FirstDownMigrationError extends Error {
1117
constructor(public migration: Migration) {
1218
super(`The first migration cannot be a down migration (${migration.name})`);
@@ -137,11 +143,11 @@ export class Task {
137143
migrationDir = config.migrationOutDir;
138144
}
139145
}
146+
const path = await this.migration.getPath(migrationDir);
140147
try {
141-
const path = await this.migration.getPath(migrationDir);
142148
migrationExports = require(path);
143149
} catch (err) {
144-
throw new MigrationNotFoundError(this.migration, migrationDir);
150+
throw new MigrationLoadError(this.migration, migrationDir, err);
145151
}
146152
if (typeof migrationExports[this.type] !== 'function') {
147153
throw new TaskTypeNotFoundError(this.migration, this.type, migrationDir);

src/test/migration.test.ts

Lines changed: 17 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
1-
import * as assert from 'assert';
1+
import * as chai from 'chai';
2+
const chaiAsPromised = require('chai-as-promised');
3+
chai.use(chaiAsPromised);
4+
const assert = chai.assert;
25
import * as fs from 'mz/fs';
36
import * as path from 'path';
47
import * as pg from 'pg';
58
import {
69
Migration,
10+
MigrationLoadError,
711
MigrationNotFoundError,
812
MigrationExecutionError,
913
TaskTypeNotFoundError,
@@ -129,61 +133,31 @@ describe('migration', () => {
129133
const task = new Task({type: 'up', migration: new Migration({name: 'not_found'})});
130134
const head = new Commit({sha1: 'HEADCOMMITSHA1'});
131135
const trigger = new Commit({sha1: 'TRIGGERCOMMITSHA1'});
132-
try {
133-
await task.execute(__dirname + '/migrations', adapter, head, trigger);
134-
throw new assert.AssertionError({
135-
message: 'No MigrationNotFoundError was thrown.'
136-
});
137-
} catch (err) {
138-
if (!(err instanceof MigrationNotFoundError)) {
139-
throw err;
140-
}
141-
}
136+
await assert.isRejected(task.execute(__dirname + '/migrations', adapter, head, trigger), MigrationNotFoundError);
137+
});
138+
it('should throw a MigrationLoadError if the migration fails loading', async () => {
139+
const task = new Task({type: 'up', migration: new Migration({name: 'error_load'})});
140+
const head = new Commit({sha1: 'HEADCOMMITSHA1'});
141+
const trigger = new Commit({sha1: 'TRIGGERCOMMITSHA1'});
142+
await assert.isRejected(task.execute(__dirname + '/migrations', adapter, head, trigger), MigrationLoadError);
142143
});
143144
it('should throw a TaskTypeNotFoundError if the migration has no up or down function', async () => {
144145
const task = new Task({type: 'up', migration: new Migration({name: 'no_up'})});
145146
const head = new Commit({sha1: 'HEADCOMMITSHA1'});
146147
const trigger = new Commit({sha1: 'TRIGGERCOMMITSHA1'});
147-
try {
148-
await task.execute(__dirname + '/migrations', adapter, head, trigger);
149-
throw new assert.AssertionError({
150-
message: 'No TaskTypeNotFoundError was thrown.'
151-
});
152-
} catch (err) {
153-
if (!(err instanceof TaskTypeNotFoundError)) {
154-
throw err;
155-
}
156-
}
148+
await assert.isRejected(task.execute(__dirname + '/migrations', adapter, head, trigger), TaskTypeNotFoundError);
157149
});
158150
it('should throw a MigrationExecutionError when the migration returns a rejected promise', async () => {
159151
const task = new Task({type: 'up', migration: new Migration({name: 'error_async'})});
160152
const head = new Commit({sha1: 'HEADCOMMITSHA1'});
161153
const trigger = new Commit({sha1: 'TRIGGERCOMMITSHA1'});
162-
try {
163-
await task.execute(__dirname + '/migrations', adapter, head, trigger);
164-
throw new assert.AssertionError({
165-
message: 'No MigrationExecutionError was thrown.'
166-
});
167-
} catch (err) {
168-
if (!(err instanceof MigrationExecutionError)) {
169-
throw err;
170-
}
171-
}
154+
await assert.isRejected(task.execute(__dirname + '/migrations', adapter, head, trigger), MigrationExecutionError);
172155
});
173156
it('should throw a MigrationExecutionError when the migration throws sync', async () => {
174157
const task = new Task({type: 'up', migration: new Migration({name: 'error_sync'})});
175158
const head = new Commit({sha1: 'HEADCOMMITSHA1'});
176159
const trigger = new Commit({sha1: 'TRIGGERCOMMITSHA1'});
177-
try {
178-
await task.execute(__dirname + '/migrations', adapter, head, trigger);
179-
throw new assert.AssertionError({
180-
message: 'No Migration ExecutionError was thrown.'
181-
});
182-
} catch (err) {
183-
if (!(err instanceof MigrationExecutionError)) {
184-
throw err;
185-
}
186-
}
160+
await assert.isRejected(task.execute(__dirname + '/migrations', adapter, head, trigger), MigrationExecutionError);
187161
});
188162
it('should throw a MigrationExecutionError when the migration would crash the process', async () => {
189163
const task = new Task({type: 'up', migration: new Migration({name: 'error_crash'})});
@@ -193,14 +167,7 @@ describe('migration', () => {
193167
try {
194168
listener = process.listeners('uncaughtException').pop();
195169
process.removeListener('uncaughtException', listener);
196-
await task.execute(__dirname + '/migrations', adapter, head, trigger);
197-
throw new assert.AssertionError({
198-
message: 'No Migration ExecutionError was thrown.'
199-
});
200-
} catch (err) {
201-
if (!(err instanceof MigrationExecutionError)) {
202-
throw err;
203-
}
170+
await assert.isRejected(task.execute(__dirname + '/migrations', adapter, head, trigger), MigrationExecutionError);
204171
} finally {
205172
process.addListener('uncaughtException', listener);
206173
}
@@ -209,16 +176,7 @@ describe('migration', () => {
209176
describe('toString', () => {
210177
it('should throw if the task type is unknown', async () => {
211178
const task = new Task(<any>{type: 'd'});
212-
try {
213-
task.toString();
214-
throw new assert.AssertionError({
215-
message: 'Task did not throw error on toString with wrong type'
216-
});
217-
} catch (err) {
218-
if (!(err instanceof UnknownTaskTypeError)) {
219-
throw err;
220-
}
221-
}
179+
assert.throws(() => task.toString(), UnknownTaskTypeError);
222180
});
223181
});
224182
});

src/test/migrations/error_load.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
2+
throw new Error('Migration failed loading');

typings.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,9 @@
1717
},
1818
"globalDevDependencies": {
1919
"mocha": "registry:env/mocha#2.2.5+20160926180742"
20+
},
21+
"devDependencies": {
22+
"chai": "registry:npm/chai#3.5.0+20160723033700",
23+
"chai-as-promised": "registry:npm/chai-as-promised#5.1.0+20160310030142"
2024
}
2125
}

0 commit comments

Comments
 (0)