142 lines
4.1 KiB
TypeScript
142 lines
4.1 KiB
TypeScript
import type { SeedRecord, SeedStore } from "../types/common.js";
|
|
|
|
/**
|
|
* Factory for generating test fixture data with auto-incrementing indices
|
|
* and customizable overrides.
|
|
*/
|
|
export class FixtureFactory<T extends SeedRecord> {
|
|
private readonly _defaults: (index: number) => T;
|
|
private _counter = 0;
|
|
|
|
constructor(defaults: (index: number) => T) {
|
|
this._defaults = defaults;
|
|
}
|
|
|
|
/** Creates a single record with optional overrides. */
|
|
create(overrides?: Partial<T>): T {
|
|
const record = this._defaults(this._counter++);
|
|
if (overrides) {
|
|
return { ...record, ...overrides };
|
|
}
|
|
return record;
|
|
}
|
|
|
|
/** Creates multiple records with optional per-record overrides. */
|
|
createMany(count: number, overrides?: Partial<T>[]): T[] {
|
|
const records: T[] = [];
|
|
for (let i = 0; i < count; i++) {
|
|
records.push(this.create(overrides?.[i]));
|
|
}
|
|
return records;
|
|
}
|
|
|
|
/** Resets the internal counter. */
|
|
reset(): void {
|
|
this._counter = 0;
|
|
}
|
|
|
|
/** Returns the current counter value. */
|
|
get counter(): number {
|
|
return this._counter;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Creates a fixture factory that generates records with incrementing IDs
|
|
* and customizable overrides.
|
|
*/
|
|
export function createFixtureFactory<T extends SeedRecord>(
|
|
defaults: (index: number) => T
|
|
): FixtureFactory<T> {
|
|
return new FixtureFactory(defaults);
|
|
}
|
|
|
|
/**
|
|
* Simple in-memory seed store for testing seed definitions without a real
|
|
* database. Implements the universal `SeedStore` contract; `AbortSignal`
|
|
* parameters are accepted for interface compatibility but ignored because
|
|
* every operation is synchronous in-memory.
|
|
*/
|
|
export class InMemorySeedStore implements SeedStore {
|
|
private readonly _tables = new Map<string, SeedRecord[]>();
|
|
|
|
async insert(
|
|
table: string,
|
|
records: readonly SeedRecord[],
|
|
_signal?: AbortSignal
|
|
): Promise<number> {
|
|
const existing = this._tables.get(table) ?? [];
|
|
existing.push(...records);
|
|
this._tables.set(table, existing);
|
|
return records.length;
|
|
}
|
|
|
|
async update(
|
|
table: string,
|
|
records: readonly SeedRecord[],
|
|
keyFields: readonly string[],
|
|
_signal?: AbortSignal
|
|
): Promise<number> {
|
|
const existing = this._tables.get(table) ?? [];
|
|
let updated = 0;
|
|
for (const record of records) {
|
|
const key = keyFields
|
|
.map((k) => String(record[k] ?? ""))
|
|
.join("::");
|
|
const idx = existing.findIndex(
|
|
(r) =>
|
|
keyFields.map((k) => String(r[k] ?? "")).join("::") === key
|
|
);
|
|
if (idx >= 0) {
|
|
existing[idx] = { ...existing[idx], ...record };
|
|
updated++;
|
|
}
|
|
}
|
|
return updated;
|
|
}
|
|
|
|
async findExisting(
|
|
table: string,
|
|
records: readonly SeedRecord[],
|
|
keyFields: readonly string[],
|
|
_signal?: AbortSignal
|
|
): Promise<SeedRecord[]> {
|
|
const existing = this._tables.get(table) ?? [];
|
|
const recordKeys = new Set(
|
|
records.map((r) =>
|
|
keyFields.map((k) => String(r[k] ?? "")).join("::")
|
|
)
|
|
);
|
|
return existing.filter((r) => {
|
|
const key = keyFields.map((k) => String(r[k] ?? "")).join("::");
|
|
return recordKeys.has(key);
|
|
});
|
|
}
|
|
|
|
async truncate(table: string, _signal?: AbortSignal): Promise<void> {
|
|
this._tables.set(table, []);
|
|
}
|
|
|
|
async tableExists(table: string, _signal?: AbortSignal): Promise<boolean> {
|
|
return this._tables.has(table);
|
|
}
|
|
|
|
/** Returns a defensive copy of all records in a table (for test assertions). */
|
|
getRecords(table: string): SeedRecord[] {
|
|
return [...(this._tables.get(table) ?? [])];
|
|
}
|
|
|
|
/** Clears all tables. */
|
|
clear(): void {
|
|
this._tables.clear();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Creates a simple in-memory seed store for testing. Records are stored in a
|
|
* map keyed by table name.
|
|
*/
|
|
export function createInMemoryStore(): InMemorySeedStore {
|
|
return new InMemorySeedStore();
|
|
}
|