balena-supervisor/test/lib/db-helper.ts
Felipe Lalanne 48e0733c7e Remove side effects for module imports
The supervisor uses the following pattern for async module
initialization

```typescript
// module.ts

export const initialised = (async () => {
    // do some async initialization
})();

// somewhere else
import * as module from 'module';

async function setup() {
  await module.initialise;
}
```

The above pattern means that whenever the module is imported, the
initialisation procedure will be ran, which is an anti-pattern.

This converts any instance of this pattern into a function

```typescript
export const initialised = _.once(async () => {
    // do some async initialization
});
```

And anywhere else on the code it replaces the call with a

```typescript
await module.initialised();
```

Change-type: patch
2022-09-06 15:48:18 -04:00

85 lines
2.4 KiB
TypeScript

import * as constants from '~/lib/constants';
import * as db from '~/src/db';
import * as sinon from 'sinon';
// Creates a test database and returns a query builder
export async function createDB() {
const oldDatabasePath = process.env.DATABASE_PATH;
// for testing we use an in memory database
process.env.DATABASE_PATH = ':memory:';
// @ts-ignore
constants.databasePath = process.env.DATABASE_PATH;
// Cleanup the module cache in order to have it reloaded in the local context
delete require.cache[require.resolve('~/src/db')];
// Initialize the database module
await db.initialized();
// Get the knex instance to allow queries to the db
const { models, upsertModel } = db;
// This is hacky but haven't found another way to do it,
// stubbing the db methods here ensures the module under test
// is using the database we want
sinon.stub(db, 'models').callsFake(models);
sinon.stub(db, 'upsertModel').callsFake(upsertModel);
return {
// Returns a query builder instance for the given
// table in order perform data operations
models,
// Resets the database to initial value post
// migrations
async reset() {
// Reset the contents of the db
await db.transaction(async (trx: any) => {
const result = await trx.raw(`
SELECT name, sql
FROM sqlite_master
WHERE type='table'`);
for (const r of result) {
// We don't run the migrations again
if (r.name !== 'knex_migrations') {
await trx.raw(`DELETE FROM ${r.name}`);
}
}
// The supervisor expects this value to already have
// been pre-populated
await trx('deviceConfig').insert({ targetValues: '{}' });
});
// Reset stub call history
(db.models as sinon.SinonStub).resetHistory();
(db.upsertModel as sinon.SinonStub).resetHistory();
},
// Destroys the in-memory database and resets environment
async destroy() {
// Remove data from the in memory database just in case
this.reset();
// Restore the old datbase path
process.env.DATABASE_PATH = oldDatabasePath;
// Restore stubs
(db.models as sinon.SinonStub).restore();
(db.upsertModel as sinon.SinonStub).restore();
// Restore the constants
// @ts-ignore
constants.databasePath = process.env.DATABASE_PATH;
// Cleanup the module cache in order to have it reloaded
// correctly next time it's used
delete require.cache[require.resolve('~/src/db')];
},
};
}
export type TestDatabase = UnwrappedPromise<ReturnType<typeof createDB>>;