232 lines
8.2 KiB
TypeScript
232 lines
8.2 KiB
TypeScript
import 'reflect-metadata';
|
|
import { getMetadataArgsStorage, QueryRunner } from 'typeorm';
|
|
import { Character } from '../../characters/entities/character.entity';
|
|
import { CreateVisibleVerticalSlice1787072400000 } from './1787072400000-CreateVisibleVerticalSlice';
|
|
import { Travel } from '../../travel/entities/travel.entity';
|
|
import { LocationConnection } from '../../world/entities/location-connection.entity';
|
|
import { LocationDefinition } from '../../world/entities/location-definition.entity';
|
|
|
|
describe('visible vertical slice schema', () => {
|
|
it('maps the location key and relationship foreign keys explicitly', () => {
|
|
const metadata = getMetadataArgsStorage();
|
|
const locationIndex = metadata.indices.find((index) => {
|
|
const metadataIndex = index as typeof index & {
|
|
options?: { unique?: boolean };
|
|
unique?: boolean;
|
|
};
|
|
|
|
return (
|
|
index.target === LocationDefinition &&
|
|
index.columns?.includes('key') &&
|
|
(metadataIndex.options?.unique ?? metadataIndex.unique) === true
|
|
);
|
|
});
|
|
|
|
expect(locationIndex).toBeDefined();
|
|
|
|
const relations = metadata.relations.filter((relation) =>
|
|
[Character, LocationConnection, Travel].includes(
|
|
relation.target as typeof Character,
|
|
),
|
|
);
|
|
const joinColumns = metadata.joinColumns
|
|
.filter((joinColumn) =>
|
|
[Character, LocationConnection, Travel].includes(
|
|
joinColumn.target as typeof Character,
|
|
),
|
|
)
|
|
.map((joinColumn) => joinColumn.name);
|
|
|
|
expect(joinColumns).toEqual(
|
|
expect.arrayContaining([
|
|
'current_location_id',
|
|
'from_location_id',
|
|
'to_location_id',
|
|
'character_id',
|
|
'origin_location_id',
|
|
'target_location_id',
|
|
]),
|
|
);
|
|
|
|
expect(
|
|
relations.map((relation) => ({
|
|
onDelete: relation.options.onDelete,
|
|
propertyName: relation.propertyName,
|
|
target: relation.target,
|
|
})),
|
|
).toEqual(
|
|
expect.arrayContaining([
|
|
expect.objectContaining({
|
|
onDelete: 'RESTRICT',
|
|
propertyName: 'currentLocation',
|
|
target: Character,
|
|
}),
|
|
expect.objectContaining({
|
|
onDelete: 'RESTRICT',
|
|
propertyName: 'fromLocation',
|
|
target: LocationConnection,
|
|
}),
|
|
expect.objectContaining({
|
|
onDelete: 'RESTRICT',
|
|
propertyName: 'toLocation',
|
|
target: LocationConnection,
|
|
}),
|
|
expect.objectContaining({
|
|
onDelete: 'RESTRICT',
|
|
propertyName: 'character',
|
|
target: Travel,
|
|
}),
|
|
expect.objectContaining({
|
|
onDelete: 'RESTRICT',
|
|
propertyName: 'originLocation',
|
|
target: Travel,
|
|
}),
|
|
expect.objectContaining({
|
|
onDelete: 'RESTRICT',
|
|
propertyName: 'targetLocation',
|
|
target: Travel,
|
|
}),
|
|
]),
|
|
);
|
|
});
|
|
|
|
it('emits reversible SQL in dependency order without extension ownership', async () => {
|
|
const query = jest.fn().mockResolvedValue(undefined);
|
|
const queryRunner = { query } as unknown as QueryRunner;
|
|
const migration = new CreateVisibleVerticalSlice1787072400000();
|
|
|
|
await migration.up(queryRunner);
|
|
|
|
const upQueries = query.mock.calls.map(([sql]) => sql as string);
|
|
|
|
expect(upQueries).toHaveLength(14);
|
|
expect(
|
|
upQueries.map((sql) => {
|
|
if (sql.includes('CREATE TABLE "location_definitions"')) {
|
|
return 'create location definitions';
|
|
}
|
|
if (sql.includes('IDX_location_definitions_key')) {
|
|
return 'create location key index';
|
|
}
|
|
if (sql.includes('CREATE TABLE "characters"')) {
|
|
return 'create characters';
|
|
}
|
|
if (sql.includes('IDX_characters_current_location')) {
|
|
return 'create character location index';
|
|
}
|
|
if (sql.includes('CREATE TABLE "location_connections"')) {
|
|
return 'create location connections';
|
|
}
|
|
if (sql.includes('IDX_location_connection_direction')) {
|
|
return 'create connection direction index';
|
|
}
|
|
if (sql.includes('IDX_location_connections_from_location')) {
|
|
return 'create connection origin index';
|
|
}
|
|
if (sql.includes('IDX_location_connections_to_location')) {
|
|
return 'create connection target index';
|
|
}
|
|
if (sql.includes('CREATE TYPE "travel_status_enum"')) {
|
|
return 'create travel status enum';
|
|
}
|
|
if (sql.includes('CREATE TABLE "travels"')) {
|
|
return 'create travels';
|
|
}
|
|
if (sql.includes('IDX_travels_character')) {
|
|
return 'create travel character index';
|
|
}
|
|
if (sql.includes('IDX_travels_origin_location')) {
|
|
return 'create travel origin index';
|
|
}
|
|
if (sql.includes('IDX_travels_target_location')) {
|
|
return 'create travel target index';
|
|
}
|
|
if (sql.includes('IDX_active_travel_per_character')) {
|
|
return 'create active travel index';
|
|
}
|
|
|
|
return 'unexpected query';
|
|
}),
|
|
).toEqual([
|
|
'create location definitions',
|
|
'create location key index',
|
|
'create characters',
|
|
'create character location index',
|
|
'create location connections',
|
|
'create connection direction index',
|
|
'create connection origin index',
|
|
'create connection target index',
|
|
'create travel status enum',
|
|
'create travels',
|
|
'create travel character index',
|
|
'create travel origin index',
|
|
'create travel target index',
|
|
'create active travel index',
|
|
]);
|
|
expect(upQueries).toEqual(
|
|
expect.arrayContaining([
|
|
expect.stringContaining('CREATE TABLE "location_definitions"'),
|
|
expect.stringContaining('CREATE TABLE "characters"'),
|
|
expect.stringContaining('CREATE TABLE "location_connections"'),
|
|
expect.stringContaining('CREATE TABLE "travels"'),
|
|
expect.stringContaining(
|
|
'CREATE UNIQUE INDEX "IDX_location_connection_direction"',
|
|
),
|
|
expect.stringContaining(
|
|
'CREATE UNIQUE INDEX "IDX_active_travel_per_character"',
|
|
),
|
|
expect.stringContaining('WHERE "status" = \'TRAVELLING\''),
|
|
]),
|
|
);
|
|
expect(upQueries[0]).toContain('CREATE TABLE "location_definitions"');
|
|
expect(upQueries).not.toEqual(
|
|
expect.arrayContaining([expect.stringContaining('CREATE EXTENSION')]),
|
|
);
|
|
expect(upQueries).not.toEqual(
|
|
expect.arrayContaining([expect.stringContaining('uuid_generate_v4')]),
|
|
);
|
|
const tableQueries = upQueries.filter((sql) =>
|
|
sql.includes('CREATE TABLE'),
|
|
);
|
|
expect(tableQueries).toHaveLength(4);
|
|
expect(
|
|
tableQueries.every((sql) => sql.includes('DEFAULT gen_random_uuid()')),
|
|
).toBe(true);
|
|
const foreignKeySql = upQueries
|
|
.filter((sql) => sql.includes('ON DELETE RESTRICT'))
|
|
.join('\n');
|
|
expect(
|
|
upQueries.filter((sql) => sql.includes('ON DELETE RESTRICT')),
|
|
).toHaveLength(3);
|
|
expect(foreignKeySql).toContain('FK_characters_current_location');
|
|
expect(foreignKeySql).toContain('FK_location_connections_from_location');
|
|
expect(foreignKeySql).toContain('FK_location_connections_to_location');
|
|
expect(foreignKeySql).toContain('FK_travels_character');
|
|
expect(foreignKeySql).toContain('FK_travels_origin_location');
|
|
expect(foreignKeySql).toContain('FK_travels_target_location');
|
|
|
|
await migration.down(queryRunner);
|
|
|
|
const downQueries = query.mock.calls
|
|
.slice(upQueries.length)
|
|
.map(([sql]) => sql as string);
|
|
|
|
expect(downQueries).toEqual([
|
|
'DROP INDEX "IDX_active_travel_per_character"',
|
|
'DROP INDEX "IDX_travels_target_location"',
|
|
'DROP INDEX "IDX_travels_origin_location"',
|
|
'DROP INDEX "IDX_travels_character"',
|
|
'DROP TABLE "travels"',
|
|
'DROP INDEX "IDX_location_connections_to_location"',
|
|
'DROP INDEX "IDX_location_connections_from_location"',
|
|
'DROP INDEX "IDX_location_connection_direction"',
|
|
'DROP TABLE "location_connections"',
|
|
'DROP INDEX "IDX_characters_current_location"',
|
|
'DROP TABLE "characters"',
|
|
'DROP INDEX "IDX_location_definitions_key"',
|
|
'DROP TABLE "location_definitions"',
|
|
'DROP TYPE "travel_status_enum"',
|
|
]);
|
|
});
|
|
});
|