@directus/api
Version:
Directus is a real-time API and App dashboard for managing SQL database content
269 lines (268 loc) • 9.59 kB
JavaScript
/**
* Knex mocking utilities for service tests
* Provides mock knex instances, table builders, and tracker utilities
*/
import { systemCollectionNames } from '@directus/system-data';
import knex from 'knex';
import { MockClient, createTracker } from 'knex-mock-client';
import { vi } from 'vitest';
/**
* Creates a mocked knex instance with tracker and schema builder support
*
* @returns Object containing the mocked db instance, tracker, and mockSchema
*
* @example
* ```typescript
* const { db, tracker, mockSchema } = createMockKnex();
*
* // Use tracker to mock query responses
* tracker.on.select('users').response([{ id: 1, name: 'John' }]);
*
* // Verify schema operations
* expect(mockSchema.createTable).toHaveBeenCalled();
* ```
*/
export function createMockKnex() {
const db = vi.mocked(knex.default({ client: MockClient }));
const tracker = createTracker(db);
// Mock schema builder methods with functional callbacks
const mockSchemaBuilder = {
createTable: vi.fn((_tableName, callback) => {
callback(createMockTableBuilder());
return Promise.resolve();
}),
dropTable: vi.fn().mockResolvedValue(undefined),
hasTable: vi.fn().mockResolvedValue(false),
table: vi.fn((_tableName, callback) => {
callback(createMockTableBuilder());
return Promise.resolve();
}),
alterTable: vi.fn((_tableName, callback) => {
callback(createMockTableBuilder());
return Promise.resolve();
}),
dropTableIfExists: vi.fn().mockResolvedValue(undefined),
renameTable: vi.fn().mockResolvedValue(undefined),
raw: vi.fn().mockResolvedValue(undefined),
};
Object.defineProperty(db, 'schema', {
get: () => mockSchemaBuilder,
configurable: true,
});
// Note: We do NOT override the query builder methods (select, where, from, etc.)
// because knex-mock-client already provides them and connects them to the tracker.
// Overriding them would break the tracker connection and cause queries to not return
// the mocked responses.
return { db, tracker, mockSchemaBuilder };
}
/**
* Creates a mock table builder for schema operations
* Used for testing column creation and alteration
*
* @returns Mock table builder with chainable methods
*
* @example
* ```typescript
* const table = createMockTableBuilder();
* table.string('name', 255).notNullable().index();
* ```
*/
export function createMockTableBuilder() {
return {
// Column types - actively used
string: vi.fn().mockReturnThis(),
text: vi.fn().mockReturnThis(),
integer: vi.fn().mockReturnThis(),
bigInteger: vi.fn().mockReturnThis(),
float: vi.fn().mockReturnThis(),
decimal: vi.fn().mockReturnThis(),
boolean: vi.fn().mockReturnThis(),
date: vi.fn().mockReturnThis(),
dateTime: vi.fn().mockReturnThis(),
timestamp: vi.fn().mockReturnThis(),
json: vi.fn().mockReturnThis(),
jsonb: vi.fn().mockReturnThis(),
uuid: vi.fn().mockReturnThis(),
// Column types - unused
// tinyint: vi.fn().mockReturnThis(),
// smallint: vi.fn().mockReturnThis(),
// mediumint: vi.fn().mockReturnThis(),
// bigint: vi.fn().mockReturnThis(),
// double: vi.fn().mockReturnThis(),
// datetime: vi.fn().mockReturnThis(),
// time: vi.fn().mockReturnThis(),
// timestamps: vi.fn().mockReturnThis(),
// binary: vi.fn().mockReturnThis(),
// enum: vi.fn().mockReturnThis(),
// enu: vi.fn().mockReturnThis(),
// geometry: vi.fn().mockReturnThis(),
// geography: vi.fn().mockReturnThis(),
// point: vi.fn().mockReturnThis(),
// linestring: vi.fn().mockReturnThis(),
// polygon: vi.fn().mockReturnThis(),
// multipoint: vi.fn().mockReturnThis(),
// multilinestring: vi.fn().mockReturnThis(),
// multipolygon: vi.fn().mockReturnThis(),
// geometrycollection: vi.fn().mockReturnThis(),
// specificType: vi.fn().mockReturnThis(),
// Auto-increment columns
increments: vi.fn().mockReturnThis(),
bigIncrements: vi.fn().mockReturnThis(),
// Column modifiers - actively used
defaultTo: vi.fn().mockReturnThis(),
notNullable: vi.fn().mockReturnThis(),
nullable: vi.fn().mockReturnThis(),
primary: vi.fn().mockReturnThis(),
unique: vi.fn().mockReturnThis(),
index: vi.fn().mockReturnThis(),
alter: vi.fn().mockReturnThis(),
// Column modifiers - unused
// unsigned: vi.fn().mockReturnThis(),
// comment: vi.fn().mockReturnThis(),
// collate: vi.fn().mockReturnThis(),
// charset: vi.fn().mockReturnThis(),
// first: vi.fn().mockReturnThis(),
// after: vi.fn().mockReturnThis(),
// references: vi.fn().mockReturnThis(),
// inTable: vi.fn().mockReturnThis(),
// onDelete: vi.fn().mockReturnThis(),
// onUpdate: vi.fn().mockReturnThis(),
// foreign: vi.fn().mockReturnThis(),
// Schema alterations - actively used
dropColumn: vi.fn().mockReturnThis(),
dropUnique: vi.fn().mockReturnThis(),
dropIndex: vi.fn().mockReturnThis(),
// Schema alterations - unused
// dropColumns: vi.fn().mockReturnThis(),
// renameColumn: vi.fn().mockReturnThis(),
// dropPrimary: vi.fn().mockReturnThis(),
// dropForeign: vi.fn().mockReturnThis(),
// dropTimestamps: vi.fn().mockReturnThis(),
// Table options - unused
// engine: vi.fn().mockReturnThis(),
// inherits: vi.fn().mockReturnThis(),
// queryContext: vi.fn().mockReturnThis(),
};
}
/**
* Sets up common database operation mock handlers for all system collections
* Automatically mocks CRUD operations (select, insert, update, delete) for all Directus system collections
*
* @param tracker The knex-mock-client tracker instance
*
* @example
* ```typescript
* const { db, tracker, mockSchema } = createMockKnex();
* setupSystemCollectionMocks(tracker);
* // Now all CRUD operations on system collections are mocked
* ```
*/
export function setupSystemCollectionMocks(tracker) {
// Mock all CRUD operations for all system collections
for (const collection of systemCollectionNames) {
tracker.on.select(collection).response([]);
tracker.on.insert(collection).response([]);
tracker.on.update(collection).response([]);
tracker.on.delete(collection).response([]);
}
}
/**
* Resets all mock states
* Should be called in afterEach hooks to clean up between tests
*
* @param tracker The knex-mock-client tracker instance
* @param mockSchema The mock schema object from createMockKnex
*
* @example
* ```typescript
* const { db, tracker, mockSchema } = createMockKnex();
*
* afterEach(() => {
* resetMocks(tracker, mockSchema);
* });
* ```
*/
export function resetKnexMocks(tracker, mockSchema) {
tracker.reset();
vi.clearAllMocks();
mockSchema.createTable.mockClear();
mockSchema.dropTable.mockClear();
mockSchema.hasTable.mockClear();
mockSchema.table.mockClear();
if (mockSchema.alterTable) {
mockSchema.alterTable.mockClear();
}
}
/**
* Creates a mock createTable function for testing table creation
* Returns a vi.fn() that calls the callback with a mock table builder
*
* @returns Mock function for db.schema.createTable
*
* @example
* ```typescript
* const { db } = createMockKnex();
* const createTableSpy = mockCreateTable();
* db.schema.createTable = createTableSpy as any;
*
* // Now when createTable is called, it will invoke the callback with a mock table builder
* await db.schema.createTable('users', (table) => {
* table.increments('id');
* table.string('name');
* });
* ```
*/
export function mockCreateTable() {
return vi.fn((_tableName, callback) => {
callback(createMockTableBuilder());
return Promise.resolve();
});
}
/**
* Creates a mock alterTable function for testing schema alterations
* Returns a vi.fn() that calls the callback with a mock table builder
*
* @returns Mock function for db.schema.alterTable
*
* @example
* ```typescript
* const { db } = createMockKnex();
* const alterTableSpy = mockAlterTable();
* db.schema.alterTable = alterTableSpy as any;
*
* // Now when alterTable is called, it will invoke the callback with a mock table builder
* await db.schema.alterTable('users', (table) => {
* table.string('name');
* });
* ```
*/
export function mockAlterTable() {
return vi.fn((_tableName, callback) => {
callback(createMockTableBuilder());
return Promise.resolve();
});
}
/**
* Creates a mock schema.table function for testing schema operations
* Returns a vi.fn() that calls the callback with a mock table builder
*
* @returns Mock function for db.schema.table
*
* @example
* ```typescript
* const { db } = createMockKnex();
* const schemaTableSpy = mockSchemaTable();
* db.schema.table = schemaTableSpy as any;
*
* // Now when schema.table is called, it will invoke the callback with a mock table builder
* await db.schema.table('users', (table) => {
* table.dropColumn('name');
* });
* ```
*/
export function mockSchemaTable() {
return vi.fn((_tableName, callback) => {
callback(createMockTableBuilder());
return Promise.resolve();
});
}