Skip to content

Commit 339b6b4

Browse files
authored
feat: Changes migrations to array to catch duplicate keys. (#7)
BREAKING CHANGE: Changes migrations to `Migration[]` instead of `MigrationDictionary`.
1 parent a9ee260 commit 339b6b4

File tree

14 files changed

+169
-125
lines changed

14 files changed

+169
-125
lines changed

src/RepoFacade.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import MigrationDictionary from './utils/types/MigrationDictionary';
1+
import Migration from './utils/types/Migration';
22
import ProcessedMigration from './utils/types/ProcessedMigration';
33

44
export default interface RepoFacade {
5-
readonly getMigrations: () => Promise<MigrationDictionary>;
5+
readonly getMigrations: () => Promise<Migration[]>;
66
readonly getProcessedMigrations: () => Promise<ProcessedMigration[]>;
77
readonly updateProcessedMigration: (migration: ProcessedMigration) => Promise<void>;
88
readonly removeProcessedMigration: (key: string) => Promise<void>;

src/factoryTest.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import TestFactory from './utils/tests/TestFactory';
77
const testFactory: TestFactory = (repoFactory) => {
88
describe('factory', () => {
99
beforeEach(async () => {
10-
await repoFactory({}).clearMigrations();
10+
await repoFactory([]).clearMigrations();
1111
});
1212

1313
migrateTest(repoFactory);

src/migrate/test.ts

Lines changed: 38 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -2,79 +2,87 @@ import * as assert from 'assert';
22
import * as assertRejects from 'assert-rejects';
33
import 'mocha'; // tslint:disable-line:no-import-side-effect
44
import factory from '../factory';
5+
import DuplicateKeyError from '../utils/errors/DuplicateKeyError';
56
import FailingMigrationError from '../utils/errors/FailingMigrationError';
67
import assertLocked from '../utils/tests/assertLocked';
78
import createMigrationProcess from '../utils/tests/createMigrationProcess';
89
import createTestUpMigration from '../utils/tests/createTestUpMigration';
910
import TestFactory from '../utils/tests/TestFactory';
10-
import MigrationDictionary from '../utils/types/MigrationDictionary';
11+
import Migration from '../utils/types/Migration';
1112

1213
const testMigrate: TestFactory = (repoFactory) => {
13-
const successfulMigration = createTestUpMigration();
14-
const failingMigration = createTestUpMigration(() => { throw new Error(); });
14+
const successfulMigration = createTestUpMigration(undefined, 'successfulMigration');
15+
const failingMigration = createTestUpMigration(() => { throw new Error(); }, 'failingMigration');
16+
const skippableKey = 'skippableMigration';
17+
const skippableMigration = createTestUpMigration(undefined, skippableKey);
1518

16-
const createService = (migrations: MigrationDictionary) => {
19+
const createService = (migrations: Migration[] = []) => {
1720
const log = () => null;
1821
return factory({ log, repo: repoFactory(migrations) });
1922
};
2023

2124
describe('migrate', () => {
2225
it('should not error when there are no migrations', async () => {
23-
const service = createService({});
26+
const service = createService();
2427
await service.migrate();
2528
});
2629

30+
it('should error when there are duplicate keys', async () => {
31+
const service = createService([createTestUpMigration(), createTestUpMigration()]);
32+
const promise = service.migrate();
33+
await assertRejects(promise, DuplicateKeyError);
34+
});
35+
2736
it('should error when the first migration errors', async () => {
28-
const promise = createService({ failingMigration, successfulMigration }).migrate();
37+
const promise = createService([failingMigration, successfulMigration]).migrate();
2938
await assertRejects(promise, FailingMigrationError);
3039
});
3140

3241
it('should error when the second migration errors', async () => {
33-
const promise = createService({ successfulMigration, failingMigration }).migrate();
42+
const promise = createService([successfulMigration, failingMigration]).migrate();
3443
await assertRejects(promise, FailingMigrationError);
3544
});
3645

3746
it('should process migrations', async () => {
3847
const { process, getProcessed } = createMigrationProcess();
39-
const service = createService({ testMigration: createTestUpMigration(process) });
48+
const service = createService([createTestUpMigration(process)]);
4049
await service.migrate();
4150
assert.equal(getProcessed(), true);
4251
});
4352

4453
it('should not reprocess migrations', async () => {
4554
const { process, getProcessed } = createMigrationProcess();
46-
await createService({ testMigration: createTestUpMigration() }).migrate();
47-
await createService({ testMigration: createTestUpMigration(process) }).migrate();
55+
await createService([createTestUpMigration()]).migrate();
56+
await createService([createTestUpMigration(process)]).migrate();
4857
assert.equal(getProcessed(), false);
4958
});
5059

5160
it('should skip processed migrations after unprocessed migrations', async () => {
52-
const skippedMigration = createMigrationProcess();
53-
const unskippedMigration = createMigrationProcess();
54-
await createService({ migrationToSkip: createTestUpMigration() }).migrate();
55-
await createService({
56-
migrationToProcess: createTestUpMigration(unskippedMigration.process),
57-
migrationToSkip: createTestUpMigration(skippedMigration.process),
58-
}).migrate();
59-
assert.equal(skippedMigration.getProcessed(), false);
60-
assert.equal(unskippedMigration.getProcessed(), true);
61+
const skippedProcess = createMigrationProcess();
62+
const unskippedProcess = createMigrationProcess();
63+
await createService([skippableMigration]).migrate();
64+
await createService([
65+
createTestUpMigration(unskippedProcess.process),
66+
createTestUpMigration(skippedProcess.process, skippableKey),
67+
]).migrate();
68+
assert.equal(skippedProcess.getProcessed(), false);
69+
assert.equal(unskippedProcess.getProcessed(), true);
6170
});
6271

6372
it('should skip processed migrations before unprocessed migrations', async () => {
64-
const skippedMigration = createMigrationProcess();
65-
const unskippedMigration = createMigrationProcess();
66-
await createService({ migrationToSkip: createTestUpMigration() }).migrate();
67-
await createService({
68-
migrationToSkip: createTestUpMigration(skippedMigration.process),
69-
// tslint:disable-next-line:object-literal-sort-keys
70-
migrationToProcess: createTestUpMigration(unskippedMigration.process),
71-
}).migrate();
72-
assert.equal(skippedMigration.getProcessed(), false);
73-
assert.equal(unskippedMigration.getProcessed(), true);
73+
const skippedProcess = createMigrationProcess();
74+
const unskippedProcess = createMigrationProcess();
75+
await createService([skippableMigration]).migrate();
76+
await createService([
77+
createTestUpMigration(skippedProcess.process, skippableKey),
78+
createTestUpMigration(unskippedProcess.process),
79+
]).migrate();
80+
assert.equal(skippedProcess.getProcessed(), false);
81+
assert.equal(unskippedProcess.getProcessed(), true);
7482
});
7583

7684
it('should error when migrations are locked', async () => {
77-
const service = createService({});
85+
const service = createService();
7886
await assertLocked([service.migrate(), service.migrate()]);
7987
});
8088
});

src/migrateByKey/test.ts

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,66 +2,73 @@ import * as assert from 'assert';
22
import * as assertRejects from 'assert-rejects';
33
import 'mocha'; // tslint:disable-line:no-import-side-effect
44
import factory from '../factory';
5+
import DuplicateKeyError from '../utils/errors/DuplicateKeyError';
56
import FailingMigrationError from '../utils/errors/FailingMigrationError';
67
import MissingMigrationError from '../utils/errors/MissingMigrationError';
78
import ProcessedMigrationError from '../utils/errors/ProcessedMigrationError';
89
import assertLocked from '../utils/tests/assertLocked';
910
import createMigrationProcess from '../utils/tests/createMigrationProcess';
10-
import createTestUpMigration from '../utils/tests/createTestUpMigration';
11+
import createTestUpMigration, { testMigrationKey } from '../utils/tests/createTestUpMigration';
1112
import TestFactory from '../utils/tests/TestFactory';
12-
import MigrationDictionary from '../utils/types/MigrationDictionary';
13+
import Migration from '../utils/types/Migration';
1314

1415
const testMigrateByKey: TestFactory = (repoFactory) => {
1516
const successfulMigration = createTestUpMigration();
1617
const failingMigration = createTestUpMigration(() => { throw new Error(); });
1718

18-
const createService = (migrations: MigrationDictionary) => {
19+
const createService = (migrations: Migration[] = []) => {
1920
const log = () => null;
2021
return factory({ log, repo: repoFactory(migrations) });
2122
};
2223

2324
describe('migrateByKey', () => {
2425
it('should error when the migration is missing', async () => {
25-
const service = createService({});
26+
const service = createService();
2627
const promise = service.migrateByKey({ key: 'missingKey' });
2728
await assertRejects(promise, MissingMigrationError);
2829
});
2930

31+
it('should error when there are duplicate keys', async () => {
32+
const service = createService([createTestUpMigration(), createTestUpMigration()]);
33+
const promise = service.migrateByKey({ key: testMigrationKey });
34+
await assertRejects(promise, DuplicateKeyError);
35+
});
36+
3037
it('should error when the migration errors', async () => {
31-
const service = createService({ failingMigration });
32-
const promise = service.migrateByKey({ key: 'failingMigration' });
38+
const service = createService([failingMigration]);
39+
const promise = service.migrateByKey({ key: testMigrationKey });
3340
await assertRejects(promise, FailingMigrationError);
3441
});
3542

3643
it('should process migration', async () => {
3744
const { process, getProcessed } = createMigrationProcess();
38-
const service = createService({ testMigration: createTestUpMigration(process) });
39-
await service.migrateByKey({ key: 'testMigration' });
45+
const service = createService([createTestUpMigration(process)]);
46+
await service.migrateByKey({ key: testMigrationKey });
4047
assert.equal(getProcessed(), true);
4148
});
4249

4350
it('should error when reprocessing migrations without force', async () => {
4451
const { process, getProcessed } = createMigrationProcess();
45-
await createService({ testMigration: createTestUpMigration() }).migrate();
46-
const service = createService({ testMigration: createTestUpMigration(process) });
47-
const promise = service.migrateByKey({ key: 'testMigration' });
52+
await createService([createTestUpMigration()]).migrate();
53+
const service = createService([createTestUpMigration(process)]);
54+
const promise = service.migrateByKey({ key: testMigrationKey });
4855
await assertRejects(promise, ProcessedMigrationError);
4956
assert.equal(getProcessed(), false);
5057
});
5158

5259
it('should reprocess migration when using force', async () => {
5360
const { process, getProcessed } = createMigrationProcess();
54-
await createService({ testMigration: createTestUpMigration() }).migrate();
55-
const service = createService({ testMigration: createTestUpMigration(process) });
56-
await service.migrateByKey({ key: 'testMigration', force: true });
61+
await createService([createTestUpMigration()]).migrate();
62+
const service = createService([createTestUpMigration(process)]);
63+
await service.migrateByKey({ key: testMigrationKey, force: true });
5764
assert.equal(getProcessed(), true);
5865
});
5966

6067
it('should error when migrations are locked', async () => {
61-
const service = createService({ successfulMigration });
68+
const service = createService([successfulMigration]);
6269
await assertLocked([
63-
service.migrateByKey({ key: 'successfulMigration' }),
64-
service.migrateByKey({ key: 'successfulMigration' }),
70+
service.migrateByKey({ key: testMigrationKey }),
71+
service.migrateByKey({ key: testMigrationKey }),
6572
]);
6673
});
6774
});

src/rollback/test.ts

Lines changed: 45 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,92 +1,104 @@
1+
// tslint:disable:max-file-line-count
12
import * as assert from 'assert';
23
import * as assertRejects from 'assert-rejects';
34
import 'mocha'; // tslint:disable-line:no-import-side-effect
45
import factory from '../factory';
6+
import DuplicateKeyError from '../utils/errors/DuplicateKeyError';
57
import FailingMigrationError from '../utils/errors/FailingMigrationError';
68
import MissingMigrationError from '../utils/errors/MissingMigrationError';
79
import assertLocked from '../utils/tests/assertLocked';
810
import createMigrationProcess from '../utils/tests/createMigrationProcess';
911
import createTestDownMigration from '../utils/tests/createTestDownMigration';
1012
import TestFactory from '../utils/tests/TestFactory';
11-
import MigrationDictionary from '../utils/types/MigrationDictionary';
13+
import Migration from '../utils/types/Migration';
1214

1315
const testRollback: TestFactory = (repoFactory) => {
14-
const successfulMigration = createTestDownMigration();
15-
const failingMigration = createTestDownMigration(() => { throw new Error(); });
16+
const successfulMigration = createTestDownMigration(undefined, 'successfulMigration');
17+
const failingMigration = createTestDownMigration(() => {
18+
throw new Error();
19+
}, 'failingMigration');
20+
const unskippableKey = 'unskippableMigration';
21+
const unskippableMigration = createTestDownMigration(undefined, unskippableKey);
1622

17-
const createService = (migrations: MigrationDictionary) => {
23+
const createService = (migrations: Migration[] = []) => {
1824
const log = () => null;
1925
return factory({ log, repo: repoFactory(migrations) });
2026
};
2127

2228
describe('rollback', () => {
2329
it('should not error when there are no migrations', async () => {
24-
const service = createService({});
30+
const service = createService();
2531
await service.rollback();
2632
});
2733

34+
it('should error when there are duplicate keys', async () => {
35+
await createService([createTestDownMigration()]).migrate();
36+
const service = createService([createTestDownMigration(), createTestDownMigration()]);
37+
const promise = service.rollback();
38+
await assertRejects(promise, DuplicateKeyError);
39+
});
40+
2841
it('should error when a processed migration is missing', async () => {
29-
await createService({ successfulMigration }).migrate();
30-
const promise = createService({}).rollback();
42+
await createService([successfulMigration]).migrate();
43+
const promise = createService().rollback();
3144
await assertRejects(promise, MissingMigrationError);
3245
});
3346

3447
it('should error when the first migration errors', async () => {
35-
const service = createService({ failingMigration, successfulMigration });
48+
const service = createService([failingMigration, successfulMigration]);
3649
await service.migrate();
3750
const promise = service.rollback();
3851
await assertRejects(promise, FailingMigrationError);
3952
});
4053

4154
it('should error when the second migration errors', async () => {
42-
const service = createService({ successfulMigration, failingMigration });
55+
const service = createService([successfulMigration, failingMigration]);
4356
await service.migrate();
4457
const promise = service.rollback();
4558
await assertRejects(promise, FailingMigrationError);
4659
});
4760

4861
it('should process rollback for a processed migration', async () => {
4962
const { process, getProcessed } = createMigrationProcess();
50-
const service = createService({ testMigration: createTestDownMigration(process) });
63+
const service = createService([createTestDownMigration(process)]);
5164
await service.migrate();
5265
await service.rollback();
5366
assert.equal(getProcessed(), true);
5467
});
5568

5669
it('should not process rollback for a unprocessed migration', async () => {
5770
const { process, getProcessed } = createMigrationProcess();
58-
const service = createService({ testMigration: createTestDownMigration(process) });
71+
const service = createService([createTestDownMigration(process)]);
5972
await service.rollback();
6073
assert.equal(getProcessed(), false);
6174
});
6275

63-
it('should skip processed migrations after unprocessed migrations', async () => {
64-
const skippedMigration = createMigrationProcess();
65-
const unskippedMigration = createMigrationProcess();
66-
await createService({ migrationToProcess: createTestDownMigration() }).migrate();
67-
await createService({
68-
migrationToProcess: createTestDownMigration(unskippedMigration.process),
69-
migrationToSkip: createTestDownMigration(skippedMigration.process),
70-
}).rollback();
71-
assert.equal(skippedMigration.getProcessed(), false);
72-
assert.equal(unskippedMigration.getProcessed(), true);
76+
it('should skip unprocessed migrations after processed migrations', async () => {
77+
const skippedProcess = createMigrationProcess();
78+
const unskippedProcess = createMigrationProcess();
79+
await createService([unskippableMigration]).migrate();
80+
await createService([
81+
createTestDownMigration(unskippedProcess.process, unskippableKey),
82+
createTestDownMigration(skippedProcess.process),
83+
]).rollback();
84+
assert.equal(skippedProcess.getProcessed(), false);
85+
assert.equal(unskippedProcess.getProcessed(), true);
7386
});
7487

75-
it('should skip processed migrations before unprocessed migrations', async () => {
76-
const skippedMigration = createMigrationProcess();
77-
const unskippedMigration = createMigrationProcess();
78-
await createService({ migrationToProcess: createTestDownMigration() }).migrate();
79-
await createService({
80-
migrationToSkip: createTestDownMigration(skippedMigration.process),
81-
// tslint:disable-next-line:object-literal-sort-keys
82-
migrationToProcess: createTestDownMigration(unskippedMigration.process),
83-
}).rollback();
84-
assert.equal(skippedMigration.getProcessed(), false);
85-
assert.equal(unskippedMigration.getProcessed(), true);
88+
it('should skip unprocessed migrations before processed migrations', async () => {
89+
const skippedProcess = createMigrationProcess();
90+
const unskippedProcess = createMigrationProcess();
91+
await createService([unskippableMigration]).migrate();
92+
await createService([
93+
createTestDownMigration(skippedProcess.process),
94+
createTestDownMigration(unskippedProcess.process, unskippableKey),
95+
]).rollback();
96+
assert.equal(skippedProcess.getProcessed(), false);
97+
assert.equal(unskippedProcess.getProcessed(), true);
8698
});
8799

88100
it('should error when migrations are locked', async () => {
89-
const service = createService({});
101+
const service = createService();
90102
await assertLocked([service.rollback(), service.rollback()]);
91103
});
92104
});

0 commit comments

Comments
 (0)