-
Notifications
You must be signed in to change notification settings - Fork 75
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
wip: add higher-level db migration tests #1252
base: master
Are you sure you want to change the base?
Conversation
@@ -0,0 +1,52 @@ | |||
global.assert = require('node:assert'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of making assert
global, could we use Should.js instead? That's what we use in other Backend tests. Or Chai would be good too, since that's the direction we're planning to go (#1167). I think it's totally OK for things like describeMigration()
in utils.js to use assert
, but I think the tests themselves should use something else.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 I'll migrate the assertions to chai
once other issues have been resolved.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@matthew-white revisiting this, a few queries:
- is this PR the appropriate place to add
chai
as a project dependency? - it helpful to introduce more uses of
should.js
when it's planned to be replaced? - is it really an issue using
assert
when it's a node built-in, and there is existing precedent for using it elsewhere in this codebase? Specifically:
lib/model/migrations/20190520-01-add-form-versioning.js
test/db-migrations/mocha-setup.js
test/e2e/oidc/playwright-tests/src/utils.js
test/e2e/standard/test.js
test/integration/other/form-purging.js
test/integration/other/submission-purging.js
test/unit/data/entity.js
test/unit/data/odata-filter.js
test/unit/model/frames/entity.js
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is this PR the appropriate place to add
chai
as a project dependency?
Yes, feel free! Or you could it add to package.json in a separate PR if that feels better. We do plan to make that switch at some point.
it helpful to introduce more uses of
should.js
when it's planned to be replaced?
You're definitely welcome to use Should.js, since that's what we currently use. Should.js and Chai are very similar, so it wouldn't take long to replace any Should.js assertions you add with Chai ones. In many/most cases, no change will be needed, e.g., .should.equal()
in Should.js is .should.equal()
in Chai.
is it really an issue using
assert
when it's a node built-in, and there is existing precedent for using it elsewhere in this codebase? Specifically:
lib/model/migrations/20190520-01-add-form-versioning.js
test/db-migrations/mocha-setup.js
test/e2e/oidc/playwright-tests/src/utils.js
test/e2e/standard/test.js
test/integration/other/form-purging.js
test/integration/other/submission-purging.js
test/unit/data/entity.js
test/unit/data/odata-filter.js
test/unit/model/frames/entity.js
Our uses of assert
have been limited, and with the exception of test/db-migrations/utils.js, I think we should try not to increase them. Almost all our Backend tests use Should.js, and our Frontend tests use Chai, which is very similar.
test/integration/ and test/unit/ just use assert
for assert.throws()
. I don't think there's a particular reason for that. We could use should.throws()
instead.
I don't mind assert
being used in files that aren't modified often, like utility functions. I just want to avoid it in actual tests that developers are more likely to read or copy from. I'm also happy with custom assertion functions being named assert*()
, e.g., assertTableExists()
. I didn't realize that test/e2e/oidc/playwright-tests/src/utils.js used assert
, but if those are utility functions, I don't think that's an issue.
On the other hand, maybe it'd be a good idea to replace uses of assert
in test/e2e/standard/test.js, since those are actual tests. I see that that file was added recently. I don't feel like there's a pressing need to make that replacement though. I'm glad we have those tests.
For many migration tests, I suspect that there won't be a need for assertions beyond those made by the assert*()
functions in utils.js. For example, test/db-migrations/20241008-01-add-user_preferences.spec.js doesn't use Should.js, but it also doesn't use assert
directly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have a few different questions, but overall this definitely looks like it's heading in the right direction to me. I like the assertion functions in utils.js, and I like the looks of the actual tests (20241008-01-add-user_preferences.spec.js). If we could get our migration tests looking like that going forward, that'd be a big improvement. 👍
function loadMigrationsList() { | ||
const migrations = fs.readdirSync(migrationsDir) | ||
.filter(f => f.endsWith('.js')) | ||
.map(f => f.replace(/\.js$/, '')) | ||
.sort(); // TODO check that this is how knex sorts migration files |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about asking Knex for the list? Something like:
const migrations = await migrator.migrate.list({ directory: migrationsDir }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd like this code to be independent of knex
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you say more about the benefit of that approach? knex
provides convenient methods for list()
and up()
. I think that whatever we replace knex
with will need to have similar functions. For example, we sometimes run up
and down
during development. By using the built-in list()
method, the code here won't need to know how Knex sorts files.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you say more about the benefit of that approach?
Work on this PR was initiated by a document titled "Removing knex from ODK Central". Not using knex
is a primary goal.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As long as we have Knex migrations in the codebase and are testing them, I think it's fair to use the knex
library. Once we're ready to remove Knex entirely (perhaps not for a while), we would remove all references to knex
.
That said, I also don't have strong feelings about this. Your current approach looks reasonable to me. 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Feels like it's getting close!
jobs: | ||
standard-tests: | ||
timeout-minutes: 2 | ||
# TODO should we use the same container as circle & central? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any downside to using the same container?
- test/db-migrations/** | ||
|
||
jobs: | ||
standard-tests: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this have a different name?
// Horrible hacks. Without this: | ||
// | ||
// 1. production migration code needs modifying, and | ||
// 2. it takes 3 mins+ just to run the migrations |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think migrations take 3 minutes to run, do they?
|
||
const previousMigration = allMigrations[idx - 1]; | ||
|
||
log('previousMigration:', previousMigration); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This logging doesn't feel super useful to me given that runIncluding()
also logs.
} | ||
} | ||
|
||
function hasRun(migratioName) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
function hasRun(migratioName) { | |
function hasRun(migrationName) { |
const existingTables = await db.oneFirst(sql`SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='public'`); | ||
if(existingTables) { | ||
console.log(` | ||
Existing tables were found in the public database schema. Reset the database before running migration tests. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In what situations will this be needed? Is the database automatically reset when tests succeed? It'd be nice to have to manually reset only rarely.
|
||
assert.strictEqual(typeof describeFn, 'function'); | ||
|
||
assert.ok(migrator.exists(migrationName), `Migration '${migrationName}' already exists.`); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Migration '${migrationName}' already exists.
Won't this throw when the migration doesn't exist? Maybe I'm misunderstanding something.
|
||
const runMigrationBeingTested = (() => { | ||
let alreadyRun; | ||
return async () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this function needs to be async. (I don't see await
.)
TODO
node:assert
chai
in testsassertIncludes()
with e.g. https://www.chaijs.com/api/assert/#method_deepincludeassertEqualInAnyOrder()
with e.g. https://www.chaijs.com/plugins/deep-equal-in-any-order/.eslintrc
Follow-up
describeMigration.only()
assertColumnExists()