Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Minor Update -> reset:data #3304

Merged
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 1 addition & 16 deletions scripts/dbManagement/addSampleData.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
import path from "node:path";
import { fileURLToPath } from "node:url";
import {
disconnect,
ensureAdministratorExists,
insertCollections,
pingDB,
} from "./helpers";
import { disconnect, insertCollections, pingDB } from "./helpers";

type Collection =
| "users"
Expand Down Expand Up @@ -35,16 +30,6 @@ export async function main(): Promise<void> {
} catch (error: unknown) {
throw new Error(`Database connection failed: ${error}`);
}
try {
await ensureAdministratorExists();
console.log("\x1b[32mSuccess:\x1b[0m Administrator setup complete\n");
} catch (error: unknown) {
console.error("\nError: Administrator creation failed", error);
throw new Error(
"\n\x1b[31mAdministrator access may be lost, try reimporting sample DB to restore access\x1b[0m\n",
);
}

try {
await insertCollections(collections);
console.log("\n\x1b[32mSuccess:\x1b[0m Sample Data added to the database");
Expand Down
76 changes: 20 additions & 56 deletions scripts/dbManagement/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import fs from "node:fs/promises";
import path from "node:path";
import readline from "node:readline";
import { fileURLToPath } from "node:url";
import { hash } from "@node-rs/argon2";
import { sql } from "drizzle-orm";
import type { AnyPgColumn, PgTable } from "drizzle-orm/pg-core";
import { drizzle } from "drizzle-orm/postgres-js";
Expand All @@ -15,7 +14,6 @@ import {
envConfigSchema,
envSchemaAjv,
} from "src/envConfigSchema";
import { uuidv7 } from "uuidv7";

const envConfig = envSchema<EnvConfig>({
ajv: envSchemaAjv,
Expand Down Expand Up @@ -68,21 +66,34 @@ export async function askUserToContinue(question: string): Promise<boolean> {
* Clears all tables in the database.
*/
export async function formatDatabase(): Promise<boolean> {
const adminEmail = envConfig.API_ADMINISTRATOR_USER_EMAIL_ADDRESS;

type TableRow = { tablename: string };

try {
await db.transaction(async (tx) => {
const tables: TableRow[] = await tx.execute(sql`
SELECT tablename FROM pg_catalog.pg_tables
WHERE schemaname = 'public'
`);
const tableNames = tables.map((row) => sql.identifier(row.tablename));
SELECT tablename FROM pg_catalog.pg_tables
WHERE schemaname = 'public'
`);
const tableNames = tables
.map((row) => row.tablename)
.filter((name) => name !== "users");

if (tableNames.length > 0) {
await tx.execute(
sql`TRUNCATE TABLE ${sql.join(tableNames, sql`, `)} RESTART IDENTITY CASCADE;`,
);
await tx.execute(sql`
TRUNCATE TABLE ${sql.join(
tableNames.map((table) => sql.identifier(table)),
sql`, `,
)}
RESTART IDENTITY CASCADE;
`);
}

await tx.execute(sql`
DELETE FROM "users"
WHERE "email_address" != ${adminEmail};
`);
});

return true;
Expand All @@ -91,53 +102,6 @@ export async function formatDatabase(): Promise<boolean> {
}
}

export async function ensureAdministratorExists(): Promise<boolean> {
const email = envConfig.API_ADMINISTRATOR_USER_EMAIL_ADDRESS;

if (!email) {
throw new Error("API_ADMINISTRATOR_USER_EMAIL_ADDRESS is not defined.");
}

const existingUser = await db.query.usersTable.findFirst({
columns: { id: true, role: true },
where: (fields, operators) => operators.eq(fields.emailAddress, email),
});

if (existingUser) {
if (existingUser.role !== "administrator") {
await db
.update(schema.usersTable)
.set({ role: "administrator" })
.where(sql`email_address = ${email}`);
console.log(
"\x1b[33mRole Change: Updated user role to administrator\x1b[0m\n",
);
} else {
console.log("\x1b[32mFound:\x1b[0m Administrator user already exists");
}
return true;
}

const userId = uuidv7();
const password = envConfig.API_ADMINISTRATOR_USER_PASSWORD;
if (!password) {
throw new Error("API_ADMINISTRATOR_USER_PASSWORD is not defined.");
}
const passwordHash = await hash(password);

await db.insert(schema.usersTable).values({
id: userId,
emailAddress: email,
name: envConfig.API_ADMINISTRATOR_USER_NAME || "",
passwordHash,
role: "administrator",
isEmailAddressVerified: true,
creatorId: userId,
});

return true;
}

export async function emptyMinioBucket(): Promise<boolean> {
try {
// List all objects in the bucket.
Expand Down
11 changes: 1 addition & 10 deletions scripts/dbManagement/resetData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import {
askUserToContinue,
disconnect,
emptyMinioBucket,
ensureAdministratorExists,
formatDatabase,
pingDB,
} from "./helpers";
Expand All @@ -26,6 +25,7 @@ export async function main(): Promise<void> {
try {
await formatDatabase();
console.log("\n\x1b[32mSuccess:\x1b[0m Database formatted successfully");
console.log("\x1b[32mSuccess:\x1b[0m Administrator access preserved\n");
} catch (error: unknown) {
console.error(
"\n\x1b[31mError: Database formatting failed\n\x1b[0m",
Expand All @@ -43,15 +43,6 @@ export async function main(): Promise<void> {
error,
);
}
try {
await ensureAdministratorExists();
console.log("\x1b[32mSuccess:\x1b[0m Administrator access restored\n");
} catch (error: unknown) {
console.error("\nError: Administrator creation failed", error);
console.error(
"\n\x1b[31mAdministrator access may be lost, try reformatting DB to restore access\x1b[0m\n",
);
}
} else {
console.log("Operation cancelled");
}
Expand Down
19 changes: 0 additions & 19 deletions test/scripts/dbManagement/addSampleData.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,6 @@ suite("addSampleData main function tests", () => {
test("should execute all operations successfully", async () => {
// Arrange: simulate successful operations.
const pingDBSpy = vi.spyOn(helpers, "pingDB").mockResolvedValue(true);
const ensureAdminSpy = vi
.spyOn(helpers, "ensureAdministratorExists")
.mockResolvedValue(true);
const insertCollectionsSpy = vi
.spyOn(helpers, "insertCollections")
.mockResolvedValue(true);
Expand All @@ -25,7 +22,6 @@ suite("addSampleData main function tests", () => {

// Assert: verify that each helper was called as expected.
expect(pingDBSpy).toHaveBeenCalled();
expect(ensureAdminSpy).toHaveBeenCalled();
expect(insertCollectionsSpy).toHaveBeenCalledWith([
"users",
"organizations",
Expand Down Expand Up @@ -60,24 +56,9 @@ suite("addSampleData main function tests", () => {
);
});

test("should throw an error when ensureAdministratorExists fails", async () => {
// Arrange: pingDB succeeds, but ensureAdministratorExists fails.
vi.spyOn(helpers, "pingDB").mockResolvedValue(true);
const errorMsg = "admin error";
vi.spyOn(helpers, "ensureAdministratorExists").mockRejectedValue(
new Error(errorMsg),
);

// Act & Assert: main() should throw the specific error message.
await expect(mainModule.main()).rejects.toThrow(
"\n\x1b[31mAdministrator access may be lost, try reimporting sample DB to restore access\x1b[0m\n",
);
});

test("should throw an error when insertCollections fails", async () => {
// Arrange: pingDB and ensureAdministratorExists succeed, but insertCollections fails.
vi.spyOn(helpers, "pingDB").mockResolvedValue(true);
vi.spyOn(helpers, "ensureAdministratorExists").mockResolvedValue(true);
const errorMsg = "insert error";
vi.spyOn(helpers, "insertCollections").mockRejectedValue(
new Error(errorMsg),
Expand Down
7 changes: 0 additions & 7 deletions test/scripts/dbManagement/resetDB.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,6 @@ suite("resetData main function tests", () => {
vi.spyOn(helpers, "emptyMinioBucket").mockRejectedValue(
new Error("minio error"),
);
vi.spyOn(helpers, "ensureAdministratorExists").mockRejectedValue(
new Error("admin error"),
);

const consoleErrorSpy = vi
.spyOn(console, "error")
Expand Down Expand Up @@ -92,7 +89,6 @@ suite("resetData main function tests", () => {
vi.spyOn(helpers, "pingDB").mockResolvedValue(true);
vi.spyOn(helpers, "formatDatabase").mockResolvedValue(true);
vi.spyOn(helpers, "emptyMinioBucket").mockResolvedValue(true);
vi.spyOn(helpers, "ensureAdministratorExists").mockResolvedValue(true);

const consoleLogSpy = vi.spyOn(console, "log").mockImplementation(() => {});

Expand All @@ -109,8 +105,5 @@ suite("resetData main function tests", () => {
expect(consoleLogSpy).toHaveBeenCalledWith(
expect.stringContaining("Bucket formatted successfully"),
);
expect(consoleLogSpy).toHaveBeenCalledWith(
expect.stringContaining("Administrator access restored"),
);
});
});
Loading