Skip to content
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

Draft
wants to merge 23 commits into
base: master
Choose a base branch
from

Conversation

alxndrsn
Copy link
Contributor

@alxndrsn alxndrsn commented Oct 29, 2024

TODO

Follow-up

  • describeMigration.only()
  • assertColumnExists()

@@ -0,0 +1,52 @@
global.assert = require('node:assert');
Copy link
Member

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.

Copy link
Contributor Author

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.

Copy link
Contributor Author

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:

  1. is this PR the appropriate place to add chai as a project dependency?
  2. it helpful to introduce more uses of should.js when it's planned to be replaced?
  3. 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

Copy link
Member

@matthew-white matthew-white Nov 14, 2024

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.

Copy link
Member

@matthew-white matthew-white left a 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. 👍

Comment on lines +74 to +78
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
Copy link
Member

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 });

Copy link
Contributor Author

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.

Copy link
Member

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.

Copy link
Contributor Author

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.

Copy link
Member

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. 👍

test/db-migrations/migrator.js Show resolved Hide resolved
test/db-migrations/migrator.js Show resolved Hide resolved
test/db-migrations/utils.js Show resolved Hide resolved
test/db-migrations/utils.js Show resolved Hide resolved
test/db-migrations/1900-test-first.spec.js Outdated Show resolved Hide resolved
config/db-migration-test.json Outdated Show resolved Hide resolved
test/db-migrations/.eslintrc.js Show resolved Hide resolved
test/db-migrations/mocha-setup.db-migrations.js Outdated Show resolved Hide resolved
test/db-migrations/utils.js Show resolved Hide resolved
Copy link
Member

@matthew-white matthew-white left a 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?
Copy link
Member

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:
Copy link
Member

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
Copy link
Member

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);
Copy link
Member

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) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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.
Copy link
Member

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.`);
Copy link
Member

@matthew-white matthew-white Nov 14, 2024

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 () => {
Copy link
Member

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.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants