From 44d3e1a5a8512a98e18a00a83fd259f6b3513eea Mon Sep 17 00:00:00 2001 From: JaiPannu-IITI Date: Sat, 15 Feb 2025 23:42:14 +0000 Subject: [PATCH] DB testing Workflow --- .github/workflows/pull-request.yml | 22 +++++ envFiles/.env.ci | 2 +- package.json | 1 + src/utilities/loadSampleData.ts | 62 +++++++++++++- src/utilities/testDbConnection.ts | 133 +++++++++++++++++++++++++++++ 5 files changed, 218 insertions(+), 2 deletions(-) create mode 100644 src/utilities/testDbConnection.ts diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index a57246446cd..62e6a668ed2 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -223,6 +223,28 @@ jobs: path: "./coverage/lcov.info" min_coverage: 39.0 + Database-Access-Check: + name: Checking database connection and access + runs-on: ubuntu-latest + needs: [Run-Tests] + steps: + - name: Checkout this repository + uses: actions/checkout@v4.2.2 + - name: Create .env file for talawa api testing environment + run: cp ./envFiles/.env.ci ./.env + - name: Building Talawa API + run: docker compose build + - name: Run Container + run: docker compose up -d + - name: Adding tables to testing environment + run: docker compose exec api pnpm apply_drizzle_test_migrations + - name: Import Sample Data into Database + run: docker compose exec api sh -c "yes yes | pnpm import:sample-data" + - name: Validate Database Records + run: docker compose exec api pnpm test:db-connection + - name: Stop Services + run: docker compose down + Test-Docusaurus-Deployment: name: Test Deployment to https://docs-api.talawa.io runs-on: ubuntu-latest diff --git a/envFiles/.env.ci b/envFiles/.env.ci index b54e851ad47..3aec45b015d 100644 --- a/envFiles/.env.ci +++ b/envFiles/.env.ci @@ -39,7 +39,7 @@ API_POSTGRES_USER=talawa # https://vitest.dev/config/#watch CI=true # https://blog.platformatic.dev/handling-environment-variables-in-nodejs#heading-set-nodeenvproduction-for-all-environments -NODE_ENV=production +NODE_ENV=test ########## docker compose `api` container service ########## diff --git a/package.json b/package.json index 5cf455b990a..b040bb634a8 100644 --- a/package.json +++ b/package.json @@ -88,6 +88,7 @@ "generate_drizzle_migrations": "drizzle-kit generate", "generate_graphql_sdl_file": "tsx ./scripts/generateGraphQLSDLFile.ts", "generate_gql_tada": "gql.tada generate-output && gql.tada turbo --fail-on-warn", + "test:db-connection": "tsx ./src/utilities/testDbConnection.ts", "import:sample-data": "tsx ./src/utilities/loadSampleData.ts", "push_drizzle_schema": "drizzle-kit push", "push_drizzle_test_schema": "drizzle-kit push --config=./test/drizzle.config.ts", diff --git a/src/utilities/loadSampleData.ts b/src/utilities/loadSampleData.ts index aa51bddbfb1..39374cc5cb5 100644 --- a/src/utilities/loadSampleData.ts +++ b/src/utilities/loadSampleData.ts @@ -1,19 +1,25 @@ import fs from "node:fs/promises"; import path from "node:path"; import { fileURLToPath } from "node:url"; +import { hash } from "@node-rs/argon2"; import dotenv from "dotenv"; import { sql } from "drizzle-orm"; import { drizzle } from "drizzle-orm/postgres-js"; import inquirer from "inquirer"; import postgres from "postgres"; +import { uuidv7 } from "uuidv7"; import * as schema from "../drizzle/schema"; dotenv.config(); const dirname: string = path.dirname(fileURLToPath(import.meta.url)); +const isTestEnvironment = process.env.NODE_ENV === "test"; + const queryClient = postgres({ - host: process.env.API_POSTGRES_HOST, + host: isTestEnvironment + ? process.env.API_POSTGRES_TEST_HOST + : process.env.API_POSTGRES_HOST, port: Number(process.env.API_POSTGRES_PORT), database: process.env.API_POSTGRES_DATABASE, username: process.env.API_POSTGRES_USER, @@ -91,11 +97,65 @@ async function formatDatabase(): Promise { * @param collections - Array of collection/table names to insert data into * @param options - Options for loading data */ + +async function ensureAdministratorExists(): Promise { + console.log("Checking if the administrator user exists..."); + + const email = process.env.API_ADMINISTRATOR_USER_EMAIL_ADDRESS; + if (!email) { + console.error( + "ERROR: API_ADMINISTRATOR_USER_EMAIL_ADDRESS is not defined.", + ); + return; + } + + 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") { + console.log("Updating user role to administrator..."); + await db + .update(schema.usersTable) + .set({ role: "administrator" }) + .where(sql`email_address = ${email}`); + console.log("Administrator role updated."); + } else { + console.log("Administrator user already exists."); + } + return; + } + + console.log("Creating administrator user..."); + const userId = uuidv7(); + const password = process.env.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: process.env.API_ADMINISTRATOR_USER_NAME || "", + passwordHash, + role: "administrator", + isEmailAddressVerified: true, + creatorId: userId, + }); + + console.log("Administrator user created successfully."); +} + async function insertCollections( collections: string[], options: LoadOptions = {}, ): Promise { try { + await ensureAdministratorExists(); + if (options.format) { await formatDatabase(); } diff --git a/src/utilities/testDbConnection.ts b/src/utilities/testDbConnection.ts new file mode 100644 index 00000000000..2d8f217e15e --- /dev/null +++ b/src/utilities/testDbConnection.ts @@ -0,0 +1,133 @@ +import dotenv from "dotenv"; +import { sql } from "drizzle-orm"; +import { drizzle } from "drizzle-orm/postgres-js"; +import postgres from "postgres"; +import * as schema from "../drizzle/schema"; + +dotenv.config(); + +const isTestEnvironment = process.env.NODE_ENV === "test"; + +// Setup PostgreSQL connection +const queryClient = postgres({ + host: isTestEnvironment + ? process.env.API_POSTGRES_TEST_HOST + : process.env.API_POSTGRES_HOST, + port: Number(process.env.API_POSTGRES_PORT), + database: process.env.API_POSTGRES_DATABASE, + username: process.env.API_POSTGRES_USER, + password: process.env.API_POSTGRES_PASSWORD, + ssl: process.env.API_POSTGRES_SSL_MODE === "true", +}); + +const db = drizzle(queryClient, { schema }); + +const expectedCounts: Record = { + users: 16, + organizations: 4, + organization_memberships: 18, +}; + +/** + * Checks record counts in specified tables after data insertion. + * @returns {Promise} - Returns true if data matches expected values. + */ +async function checkCountAfterImport(): Promise { + try { + const tables = [ + { name: "users", table: schema.usersTable }, + { name: "organizations", table: schema.organizationsTable }, + { + name: "organization_memberships", + table: schema.organizationMembershipsTable, + }, + ]; + let allValid = true; + for (const { name, table } of tables) { + const result = await db + .select({ count: sql`count(*)` }) + .from(table); + const actualCount = Number(result[0]?.count ?? 0); // Convert actual count to number + const expectedCount = expectedCounts[name]; // Expected count is already a number + + if (actualCount !== expectedCount) { + console.error( + `ERROR: Record count mismatch in ${name} (Expected ${expectedCount}, Found ${actualCount})`, + ); + allValid = false; + } + } + + return allValid; + } catch (error) { + console.error(`ERROR: ${error}`); + return false; + } +} + +/** + * Makes an update in the database (Modify the first user's name). + * @returns {Promise} - Returns true if the update was successful. + */ +async function updateDatabase(): Promise { + const updatedName = "Test User"; + + try { + const user = await db.select().from(schema.usersTable).limit(1); + if (user.length === 0) { + console.error("ERROR: No user found to update!"); + return false; + } + + const userId = user[0]?.id; + + await db + .update(schema.usersTable) + .set({ name: updatedName }) + .where(sql`id = ${userId}`); + + const updatedUser = await db + .select() + .from(schema.usersTable) + .where(sql`id = ${userId}`); + + if (updatedUser[0]?.name !== updatedName) { + console.error("ERROR: Database update failed!"); + return false; + } + return true; + } catch (error) { + console.error(`ERROR: ${error}`); + return false; + } +} + +/** + * Runs the validation and update process. + */ +async function runValidation(): Promise { + try { + const validRecords = await checkCountAfterImport(); + if (!validRecords) { + console.error("\nERROR: Database validation failed!"); + process.exit(1); + } + console.log("\nDatabase Validation : Success"); + const updateSuccess = await updateDatabase(); + if (!updateSuccess) { + console.error("\nERROR: Database update validation failed!"); + process.exit(1); + } + console.log("Database Updation : Success"); + process.exit(0); + } catch (error) { + if (error instanceof Error) { + console.error(`\nERROR: ${error.message}`); + } else { + console.error(`\nERROR: ${String(error)}`); + } + process.exit(1); + } +} + +runValidation();