feat: implementar pruebas para la inicialización de usuarios y estructuras en la base de datos
This commit is contained in:
parent
e4d355d00f
commit
8fcbf422f4
206
server/src/infrastructure/http/server.test.ts
Normal file
206
server/src/infrastructure/http/server.test.ts
Normal file
@ -0,0 +1,206 @@
|
||||
import { EventEmitter } from "events";
|
||||
|
||||
const sync = jest.fn();
|
||||
const closeDatabase = jest.fn();
|
||||
const createAdapter = jest.fn();
|
||||
const updateDealer = jest.fn();
|
||||
const updateQuote = jest.fn();
|
||||
const prepareStructure = jest.fn();
|
||||
const prepareUsers = jest.fn();
|
||||
const logger = { info: jest.fn(), warn: jest.fn(), error: jest.fn() };
|
||||
const server = Object.assign(new EventEmitter(), {
|
||||
listening: false,
|
||||
listen: jest.fn(),
|
||||
close: jest.fn(),
|
||||
});
|
||||
|
||||
jest.mock("http", () => ({ createServer: () => server }));
|
||||
jest.mock("@/config", () => ({ config: { environment: "test", server: { port: 3001 } } }));
|
||||
jest.mock("../express/app", () => ({ app: {} }));
|
||||
jest.mock("../logger", () => ({ initLogger: () => logger }));
|
||||
jest.mock("@/contexts/common/infrastructure/sequelize", () => ({ createSequelizeAdapter: () => createAdapter() }));
|
||||
jest.mock("@/contexts/sales/infrastructure/sequelize/dealer.model", () => ({
|
||||
DEFAULT_TAX_LABEL: "% IVA", Dealer_Model: { update: (...args: unknown[]) => updateDealer(...args) },
|
||||
}));
|
||||
jest.mock("@/contexts/sales/infrastructure/sequelize/quote.model", () => ({
|
||||
DEFAULT_QUOTE_TAX_LABEL: "% IVA", Quote_Model: { update: (...args: unknown[]) => updateQuote(...args) },
|
||||
}));
|
||||
jest.mock("../sequelize", () => ({
|
||||
initStructure: (...args: unknown[]) => prepareStructure(...args),
|
||||
insertUsers: () => prepareUsers(),
|
||||
}));
|
||||
|
||||
const originalExitCode = process.exitCode;
|
||||
let handlers: Record<string, (...args: unknown[]) => void>;
|
||||
let exit: jest.SpyInstance;
|
||||
const flush = () => new Promise<void>((resolve) => setImmediate(resolve));
|
||||
const loadServer = () => { jest.isolateModules(() => { require("./server"); }); };
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
process.exitCode = undefined;
|
||||
server.removeAllListeners();
|
||||
server.listening = false;
|
||||
handlers = {};
|
||||
jest.spyOn(process, "on").mockImplementation(((event: string, handler: (...args: unknown[]) => void) => {
|
||||
handlers[event] = handler;
|
||||
return process;
|
||||
}) as typeof process.on);
|
||||
jest.spyOn(process, "once").mockImplementation(((event: string, handler: (...args: unknown[]) => void) => {
|
||||
handlers[event] = handler;
|
||||
return process;
|
||||
}) as typeof process.once);
|
||||
exit = jest.spyOn(process, "exit").mockImplementation((() => undefined) as never);
|
||||
sync.mockResolvedValue(undefined);
|
||||
closeDatabase.mockResolvedValue(undefined);
|
||||
createAdapter.mockReturnValue({ sync, connection: { close: closeDatabase } });
|
||||
updateDealer.mockResolvedValue(undefined);
|
||||
updateQuote.mockResolvedValue(undefined);
|
||||
prepareStructure.mockResolvedValue(undefined);
|
||||
prepareUsers.mockResolvedValue(undefined);
|
||||
server.listen.mockImplementation(() => {
|
||||
server.listening = true;
|
||||
server.emit("listening");
|
||||
});
|
||||
server.close.mockImplementation((callback: (error?: Error) => void) => {
|
||||
server.listening = false;
|
||||
callback();
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
jest.useRealTimers();
|
||||
process.exitCode = originalExitCode;
|
||||
});
|
||||
|
||||
it("waits for each initialization step before opening HTTP", async () => {
|
||||
const order: string[] = [];
|
||||
for (const [name, step] of [
|
||||
["sync", sync], ["dealer", updateDealer], ["quote", updateQuote],
|
||||
["structure", prepareStructure], ["users", prepareUsers],
|
||||
] as const) step.mockImplementation(async () => { order.push(name); });
|
||||
server.listen.mockImplementation(() => { order.push("listen"); server.emit("listening"); });
|
||||
|
||||
loadServer();
|
||||
await flush();
|
||||
|
||||
expect(order).toEqual(["sync", "dealer", "quote", "structure", "users", "listen"]);
|
||||
expect(sync).toHaveBeenCalledWith({ force: false, alter: true });
|
||||
expect(exit).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it.each(["structure", "users"])("does not listen while %s initialization is pending", async (stage) => {
|
||||
let finish!: () => void;
|
||||
const step = stage === "structure" ? prepareStructure : prepareUsers;
|
||||
step.mockReturnValue(new Promise<void>((resolve) => { finish = resolve; }));
|
||||
loadServer();
|
||||
await flush();
|
||||
expect(server.listen).not.toHaveBeenCalled();
|
||||
finish();
|
||||
await flush();
|
||||
expect(server.listen).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it.each(["sync", "dealer", "quote", "structure", "users"])("closes the database and exits on %s failure without opening HTTP", async (stage) => {
|
||||
const steps = { sync, dealer: updateDealer, quote: updateQuote, structure: prepareStructure, users: prepareUsers };
|
||||
const failure = new Error("Initialization failed");
|
||||
steps[stage as keyof typeof steps].mockRejectedValue(failure);
|
||||
loadServer();
|
||||
await flush();
|
||||
expect(server.listen).not.toHaveBeenCalled();
|
||||
expect(server.close).not.toHaveBeenCalled();
|
||||
expect(closeDatabase).toHaveBeenCalledTimes(1);
|
||||
expect(exit).toHaveBeenCalledWith(1);
|
||||
expect(logger.error).toHaveBeenCalledWith("Server startup or execution failed", failure);
|
||||
});
|
||||
|
||||
it("handles synchronous adapter creation failure", async () => {
|
||||
createAdapter.mockImplementationOnce(() => { throw new Error("Adapter failed"); });
|
||||
loadServer();
|
||||
await flush();
|
||||
expect(server.listen).not.toHaveBeenCalled();
|
||||
expect(exit).toHaveBeenCalledWith(1);
|
||||
});
|
||||
|
||||
it("handles an occupied port and closes the database without closing an unstarted HTTP server", async () => {
|
||||
server.listen.mockImplementation(() => { server.emit("error", Object.assign(new Error("Port occupied"), { code: "EADDRINUSE" })); });
|
||||
loadServer();
|
||||
await flush();
|
||||
expect(server.close).not.toHaveBeenCalled();
|
||||
expect(closeDatabase).toHaveBeenCalledTimes(1);
|
||||
expect(exit).toHaveBeenCalledWith(1);
|
||||
});
|
||||
|
||||
it.each(["SIGINT", "SIGTERM"])("closes HTTP then the database on %s, only once", async (signal) => {
|
||||
loadServer();
|
||||
await flush();
|
||||
handlers[signal]();
|
||||
handlers[signal]();
|
||||
await flush();
|
||||
expect(server.close).toHaveBeenCalledTimes(1);
|
||||
expect(closeDatabase).toHaveBeenCalledTimes(1);
|
||||
expect(server.close.mock.invocationCallOrder[0]).toBeLessThan(closeDatabase.mock.invocationCallOrder[0]);
|
||||
expect(exit).toHaveBeenCalledWith(0);
|
||||
});
|
||||
|
||||
it.each(["uncaughtException", "unhandledRejection"])("terminates on %s instead of continuing to serve", async (event) => {
|
||||
loadServer();
|
||||
await flush();
|
||||
handlers[event](new Error("Fatal failure"));
|
||||
await flush();
|
||||
expect(server.close).toHaveBeenCalledTimes(1);
|
||||
expect(closeDatabase).toHaveBeenCalledTimes(1);
|
||||
expect(exit).toHaveBeenCalledWith(1);
|
||||
});
|
||||
|
||||
it("handles HTTP errors after startup", async () => {
|
||||
loadServer();
|
||||
await flush();
|
||||
server.emit("error", new Error("HTTP failure"));
|
||||
await flush();
|
||||
expect(exit).toHaveBeenCalledWith(1);
|
||||
expect(closeDatabase).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("still closes the database if HTTP close fails", async () => {
|
||||
server.close.mockImplementation((callback: (error?: Error) => void) => callback(new Error("Close failed")));
|
||||
loadServer();
|
||||
await flush();
|
||||
handlers.SIGTERM();
|
||||
await flush();
|
||||
expect(closeDatabase).toHaveBeenCalledTimes(1);
|
||||
expect(exit).toHaveBeenCalledWith(1);
|
||||
});
|
||||
|
||||
it("exits with an error if database close fails", async () => {
|
||||
closeDatabase.mockRejectedValue(new Error("Close failed"));
|
||||
loadServer();
|
||||
await flush();
|
||||
handlers.SIGTERM();
|
||||
await flush();
|
||||
expect(exit).toHaveBeenCalledWith(1);
|
||||
});
|
||||
|
||||
it("does not open HTTP if interrupted during initialization", async () => {
|
||||
let finish!: () => void;
|
||||
prepareStructure.mockReturnValue(new Promise<void>((resolve) => { finish = resolve; }));
|
||||
loadServer();
|
||||
await flush();
|
||||
handlers.SIGTERM();
|
||||
finish();
|
||||
await flush();
|
||||
expect(server.listen).not.toHaveBeenCalled();
|
||||
expect(prepareUsers).not.toHaveBeenCalled();
|
||||
expect(exit).toHaveBeenCalledWith(0);
|
||||
});
|
||||
|
||||
it("forces an error exit when HTTP shutdown exceeds the deadline", async () => {
|
||||
loadServer();
|
||||
await flush();
|
||||
jest.useFakeTimers();
|
||||
server.close.mockImplementation(() => undefined);
|
||||
handlers.SIGTERM();
|
||||
jest.advanceTimersByTime(30000);
|
||||
expect(exit).toHaveBeenCalledWith(1);
|
||||
});
|
||||
@ -1,4 +1,3 @@
|
||||
/* eslint-disable no-use-before-define */
|
||||
import rTracer from "cls-rtracer";
|
||||
import http from "http";
|
||||
import { assign } from "lodash";
|
||||
@ -13,7 +12,6 @@ import {
|
||||
DEFAULT_QUOTE_TAX_LABEL,
|
||||
Quote_Model,
|
||||
} from "@/contexts/sales/infrastructure/sequelize/quote.model";
|
||||
import { trace } from "console";
|
||||
import { config } from "../../config";
|
||||
import { app } from "../express/app";
|
||||
import { initLogger } from "../logger";
|
||||
@ -37,55 +35,6 @@ export const currentState = assign(
|
||||
config
|
||||
);
|
||||
|
||||
const serverStop = (server: http.Server) => {
|
||||
const forceTimeout = 30000;
|
||||
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
logger.warn("⚡️ Shutting down server");
|
||||
|
||||
setTimeout(() => {
|
||||
logger.error("Could not close connections in time, forcefully shutting down");
|
||||
resolve();
|
||||
}, forceTimeout).unref();
|
||||
|
||||
server.close((err) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
|
||||
logger.info("Closed out remaining connections.");
|
||||
logger.info("Bye!");
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
/*const now = DateTime.now();
|
||||
// Destroy server and available connections.
|
||||
logger.info(`Time: ${now.toLocaleString()}`);
|
||||
logger.info('Shutting down at: ' + new Date());
|
||||
if (server) {
|
||||
server.close();
|
||||
}
|
||||
|
||||
logger.info('Bye!');
|
||||
process.exit(1);*/
|
||||
};
|
||||
|
||||
const serverError = (error: any) => {
|
||||
if (error.code === "EADDRINUSE") {
|
||||
logger.debug(`⛔️ Server wasn't able to start properly.`);
|
||||
logger.error(`The port ${error.port} is already used by another application.`);
|
||||
} else {
|
||||
logger.debug(`⛔️ Server wasn't able to start properly.`);
|
||||
logger.error(error);
|
||||
trace(error);
|
||||
}
|
||||
|
||||
serverStop(server);
|
||||
|
||||
return;
|
||||
};
|
||||
|
||||
const serverConnection = (conn: any) => {
|
||||
const key = `${conn.remoteAddress}:${conn.remotePort}`;
|
||||
currentState.connections[key] = conn;
|
||||
@ -95,57 +44,108 @@ const serverConnection = (conn: any) => {
|
||||
});
|
||||
};
|
||||
|
||||
const sequelizeConn = createSequelizeAdapter();
|
||||
//const firebirdConn = createFirebirdAdapter();
|
||||
let sequelizeConn: ReturnType<typeof createSequelizeAdapter> | undefined;
|
||||
let stopping = false;
|
||||
|
||||
const server: http.Server = http
|
||||
const server = http
|
||||
.createServer(app)
|
||||
.once("listening", () =>
|
||||
process.on("SIGINT", () => {
|
||||
//firebirdConn.disconnect();
|
||||
serverStop(server);
|
||||
})
|
||||
)
|
||||
.on("close", () =>
|
||||
logger.info(`Shut down at: ${DateTime.now().toLocaleString(DateTime.DATETIME_FULL)}`)
|
||||
)
|
||||
.on("connection", serverConnection)
|
||||
.on("error", serverError);
|
||||
.on("connection", serverConnection);
|
||||
|
||||
const stopServer = async (exitCode: number) => {
|
||||
if (exitCode !== 0) process.exitCode = exitCode;
|
||||
if (stopping) return;
|
||||
stopping = true;
|
||||
logger.warn("⚡️ Shutting down server");
|
||||
|
||||
const timeout = setTimeout(() => {
|
||||
logger.error("Could not close connections in time, forcefully shutting down");
|
||||
process.exit(1);
|
||||
}, 30000);
|
||||
timeout.unref();
|
||||
|
||||
try {
|
||||
if (server.listening) {
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
server.close((error) => (error ? reject(error) : resolve()));
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
process.exitCode = 1;
|
||||
logger.error("Could not close HTTP server", error);
|
||||
}
|
||||
|
||||
try {
|
||||
await sequelizeConn?.connection.close();
|
||||
} catch (error) {
|
||||
process.exitCode = 1;
|
||||
logger.error("Could not close database connection", error);
|
||||
} finally {
|
||||
clearTimeout(timeout);
|
||||
process.exit(process.exitCode || exitCode);
|
||||
}
|
||||
};
|
||||
|
||||
const failServer = (error: unknown) => {
|
||||
logger.error("Server startup or execution failed", error);
|
||||
void stopServer(1);
|
||||
};
|
||||
|
||||
process.once("SIGINT", () => { void stopServer(0); });
|
||||
process.once("SIGTERM", () => { void stopServer(0); });
|
||||
process.on("uncaughtException", failServer);
|
||||
process.on("unhandledRejection", failServer);
|
||||
|
||||
const startServer = async () => {
|
||||
try {
|
||||
sequelizeConn = createSequelizeAdapter();
|
||||
await sequelizeConn.sync({ force: false, alter: true });
|
||||
if (stopping) return;
|
||||
|
||||
try {
|
||||
//firebirdConn.sync().then(() => {
|
||||
sequelizeConn.sync({ force: false, alter: true }).then(async () => {
|
||||
await Dealer_Model.update(
|
||||
{ default_tax_label: DEFAULT_TAX_LABEL },
|
||||
{ where: { default_tax_label: null } }
|
||||
);
|
||||
if (stopping) return;
|
||||
await Quote_Model.update(
|
||||
{ tax_label: DEFAULT_QUOTE_TAX_LABEL },
|
||||
{ where: { tax_label: null } }
|
||||
);
|
||||
if (stopping) return;
|
||||
|
||||
initStructure(sequelizeConn.connection);
|
||||
insertUsers();
|
||||
await initStructure(sequelizeConn.connection);
|
||||
if (stopping) return;
|
||||
await insertUsers();
|
||||
if (stopping) return;
|
||||
|
||||
// Launch server
|
||||
server.listen(currentState.server.port, () => {
|
||||
const now = DateTime.now();
|
||||
logger.info(`Time: ${now.toLocaleString(DateTime.DATETIME_FULL)} ${now.zoneName}`);
|
||||
logger.info(`Launched in: ${now.diff(currentState.launchedAt).toMillis()} ms`);
|
||||
logger.info(`Environment: ${currentState.environment}`);
|
||||
logger.info(`Process PID: ${process.pid}`);
|
||||
logger.info("To shut down your server, press <CTRL> + C at any time");
|
||||
logger.info(`⚡️ Server: http://${currentState.server.hostname}:${currentState.server.port}`);
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
const onError = (error: Error) => {
|
||||
server.removeListener("listening", onListening);
|
||||
reject(error);
|
||||
};
|
||||
const onListening = () => {
|
||||
server.removeListener("error", onError);
|
||||
server.on("error", failServer);
|
||||
resolve();
|
||||
};
|
||||
server.once("error", onError);
|
||||
server.once("listening", onListening);
|
||||
server.listen(currentState.server.port);
|
||||
});
|
||||
});
|
||||
//});
|
||||
} catch (error) {
|
||||
serverError(error);
|
||||
}
|
||||
if (stopping) return;
|
||||
|
||||
process.on("uncaughtException", (error: any) => {
|
||||
//firebirdConn.disconnect();
|
||||
logger.error(`${new Date().toUTCString()} uncaughtException:`, error.message);
|
||||
logger.error(error.stack);
|
||||
//process.exit(1);
|
||||
});
|
||||
const now = DateTime.now();
|
||||
logger.info(`Time: ${now.toLocaleString(DateTime.DATETIME_FULL)} ${now.zoneName}`);
|
||||
logger.info(`Launched in: ${now.diff(currentState.launchedAt).toMillis()} ms`);
|
||||
logger.info(`Environment: ${currentState.environment}`);
|
||||
logger.info(`Process PID: ${process.pid}`);
|
||||
logger.info("To shut down your server, press <CTRL> + C at any time");
|
||||
logger.info(`⚡️ Server: http://${currentState.server.hostname}:${currentState.server.port}`);
|
||||
} catch (error) {
|
||||
failServer(error);
|
||||
}
|
||||
};
|
||||
|
||||
void startServer();
|
||||
|
||||
56
server/src/infrastructure/sequelize/initData.test.ts
Normal file
56
server/src/infrastructure/sequelize/initData.test.ts
Normal file
@ -0,0 +1,56 @@
|
||||
import { insertUsers } from "./initData";
|
||||
import { initializeAdmin, initializeSampleDealer, initializeSampleUser } from "@/contexts/users/application/userServices";
|
||||
|
||||
jest.mock("@/config", () => ({ config: { defaults: {} } }));
|
||||
jest.mock("@/contexts/common/domain", () => ({ RepositoryManager: { getInstance: () => ({ getRepository: jest.fn() }) } }));
|
||||
jest.mock("@/contexts/common/infrastructure/sequelize", () => ({ createSequelizeAdapter: jest.fn() }));
|
||||
jest.mock("@/contexts/sales/infrastructure/Dealer.repository", () => ({ registerDealerRepository: jest.fn() }));
|
||||
jest.mock("@/contexts/users/infrastructure/User.repository", () => ({ registerUserRepository: jest.fn() }));
|
||||
jest.mock("@/contexts/users/application/userServices", () => ({
|
||||
initializeAdmin: jest.fn(), initializeSampleUser: jest.fn(), initializeSampleDealer: jest.fn(),
|
||||
}));
|
||||
const admin = jest.mocked(initializeAdmin);
|
||||
const user = jest.mocked(initializeSampleUser);
|
||||
const dealer = jest.mocked(initializeSampleDealer);
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
admin.mockResolvedValue(undefined);
|
||||
user.mockResolvedValue(false);
|
||||
dealer.mockResolvedValue(undefined);
|
||||
});
|
||||
|
||||
it("awaits admin initialization before initializing the sample user", async () => {
|
||||
let finish!: () => void;
|
||||
admin.mockReturnValue(new Promise<void>((resolve) => { finish = resolve; }));
|
||||
const pending = insertUsers();
|
||||
expect(user).not.toHaveBeenCalled();
|
||||
finish();
|
||||
await pending;
|
||||
expect(user).toHaveBeenCalledTimes(1);
|
||||
expect(dealer).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it.each(["admin", "user", "dealer"])("propagates %s initialization errors", async (stage) => {
|
||||
user.mockResolvedValue({} as Exclude<Awaited<ReturnType<typeof initializeSampleUser>>, boolean>);
|
||||
const failure = new Error("Initialization failed");
|
||||
({ admin, user, dealer })[stage as "admin" | "user" | "dealer"].mockRejectedValue(failure);
|
||||
await expect(insertUsers()).rejects.toBe(failure);
|
||||
expect(user).toHaveBeenCalledTimes(stage === "admin" ? 0 : 1);
|
||||
expect(dealer).toHaveBeenCalledTimes(stage === "dealer" ? 1 : 0);
|
||||
});
|
||||
|
||||
it("does not complete until the sample dealer is initialized", async () => {
|
||||
user.mockResolvedValue({} as Exclude<Awaited<ReturnType<typeof initializeSampleUser>>, boolean>);
|
||||
let finish!: () => void;
|
||||
dealer.mockReturnValue(new Promise<void>((resolve) => { finish = resolve; }));
|
||||
let completed = false;
|
||||
const pending = insertUsers().then(() => { completed = true; });
|
||||
await Promise.resolve();
|
||||
await Promise.resolve();
|
||||
expect(dealer).toHaveBeenCalledTimes(1);
|
||||
expect(completed).toBe(false);
|
||||
finish();
|
||||
await pending;
|
||||
expect(completed).toBe(true);
|
||||
});
|
||||
@ -20,14 +20,14 @@ export const insertUsers = async () => {
|
||||
registerUserRepository(context);
|
||||
registerDealerRepository(context);
|
||||
|
||||
initializeAdmin(context.adapter, context.repositoryManager.getRepository("User"));
|
||||
await initializeAdmin(context.adapter, context.repositoryManager.getRepository("User"));
|
||||
const user = await initializeSampleUser(
|
||||
context.adapter,
|
||||
context.repositoryManager.getRepository("User")
|
||||
);
|
||||
|
||||
if (user) {
|
||||
initializeSampleDealer(
|
||||
await initializeSampleDealer(
|
||||
user,
|
||||
context.adapter,
|
||||
context.repositoryManager.getRepository("Dealer")
|
||||
|
||||
32
server/src/infrastructure/sequelize/initStructure.test.ts
Normal file
32
server/src/infrastructure/sequelize/initStructure.test.ts
Normal file
@ -0,0 +1,32 @@
|
||||
import { Sequelize } from "sequelize";
|
||||
import { initStructure } from "./initStructure";
|
||||
|
||||
jest.mock("@/infrastructure/logger", () => ({ initLogger: () => ({ debug: jest.fn(), error: jest.fn() }) }));
|
||||
|
||||
it.each(["catalog", "catalog_translations", "v_catalog"])("propagates %s creation errors and stops initialization", async (object) => {
|
||||
const failure = new Error("DDL failed");
|
||||
const query = jest.fn(async (sql: string) => {
|
||||
if (sql.includes("SELECT COUNT(*)")) return [[{ count: 0 }]];
|
||||
if (sql.includes(`CREATE TABLE ${object} (`) || sql.includes(`CREATE VIEW ${object} AS`)) throw failure;
|
||||
return [];
|
||||
});
|
||||
await expect(initStructure({ query } as unknown as Sequelize)).rejects.toBe(failure);
|
||||
const lastSQL = query.mock.calls[query.mock.calls.length - 1][0];
|
||||
expect(lastSQL).toContain(object === "v_catalog" ? `CREATE VIEW ${object}` : `CREATE TABLE ${object}`);
|
||||
});
|
||||
|
||||
it("propagates metadata query errors", async () => {
|
||||
const failure = new Error("Metadata failed");
|
||||
const query = jest.fn().mockRejectedValue(failure);
|
||||
await expect(initStructure({ query } as unknown as Sequelize)).rejects.toBe(failure);
|
||||
expect(query).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("awaits catalog tables and view creation in order", async () => {
|
||||
const query = jest.fn(async (sql: string) => sql.includes("SELECT COUNT(*)") ? [[{ count: 0 }]] : []);
|
||||
await initStructure({ query } as unknown as Sequelize);
|
||||
expect(query).toHaveBeenCalledTimes(6);
|
||||
expect(query.mock.calls[1][0]).toContain("CREATE TABLE catalog (");
|
||||
expect(query.mock.calls[3][0]).toContain("CREATE TABLE catalog_translations (");
|
||||
expect(query.mock.calls[5][0]).toContain("CREATE VIEW v_catalog AS");
|
||||
});
|
||||
@ -49,6 +49,7 @@ const createCatalogTableIfNotExists = async (sequelize: Sequelize) => {
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Error al crear la tabla "catalog":', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
@ -97,6 +98,7 @@ const createCatalogTranslationsTableIfNotExists = async (sequelize: Sequelize) =
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Error al crear la tabla "catalog_translations":', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
@ -138,10 +140,11 @@ const createVCatalogViewIfNotExists = async (sequelize: Sequelize) => {
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Error al crear la vista "v_catalog":', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const initStructure = async (sequelize) => {
|
||||
export const initStructure = async (sequelize: Sequelize) => {
|
||||
await createCatalogTableIfNotExists(sequelize);
|
||||
await createCatalogTranslationsTableIfNotExists(sequelize);
|
||||
await createVCatalogViewIfNotExists(sequelize);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user