diff --git a/containers/ecr-viewer/docker-compose.yml b/containers/ecr-viewer/docker-compose.yml index c189c292b8..a76760c0d9 100644 --- a/containers/ecr-viewer/docker-compose.yml +++ b/containers/ecr-viewer/docker-compose.yml @@ -64,10 +64,9 @@ services: test: [ "CMD-SHELL", - "/opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P Password1! -Q 'IF EXISTS (SELECT 1 FROM sys.tables WHERE name = 'ECR_DATA') SELECT 1 ELSE SELECT 0' -C | grep -q 1", + 'opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P Password1! -Q "SELECT * FROM sys.tables where name=''ECR_DATA''" -C | grep -q "(0 rows affected)" && exit 1 || exit 0', ] interval: 20s - timeout: 10s retries: 5 profiles: - sqlserver diff --git a/containers/ecr-viewer/src/app/api/conditions/route.ts b/containers/ecr-viewer/src/app/api/conditions/route.ts index cc1206d466..5fd44e3285 100644 --- a/containers/ecr-viewer/src/app/api/conditions/route.ts +++ b/containers/ecr-viewer/src/app/api/conditions/route.ts @@ -6,17 +6,30 @@ import { get_conditions_postgres, get_conditions_sqlserver } from "./service"; * @returns A promise resolving to a NextResponse object. */ export async function GET() { - const metadataSaveLocation = process.env.METADATA_DATABASE_TYPE; + const metadataLocation = process.env.METADATA_DATABASE_TYPE; - switch (metadataSaveLocation) { - case "postgres": - return await get_conditions_postgres(); - case "sqlserver": - return await get_conditions_sqlserver(); - default: - return NextResponse.json( - { message: "Invalid metadata save location: " + metadataSaveLocation }, - { status: 500 }, - ); + try { + switch (metadataLocation) { + case "postgres": + return NextResponse.json(await get_conditions_postgres(), { + status: 200, + }); + case "sqlserver": + return NextResponse.json(await get_conditions_sqlserver(), { + status: 200, + }); + default: + return NextResponse.json( + { + message: "Invalid metadata location.", + }, + { status: 500 }, + ); + } + } catch (e) { + return NextResponse.json( + { message: "Failed to get conditions." }, + { status: 500 }, + ); } } diff --git a/containers/ecr-viewer/src/app/api/conditions/service.ts b/containers/ecr-viewer/src/app/api/conditions/service.ts index 842143a5e3..1f315be370 100644 --- a/containers/ecr-viewer/src/app/api/conditions/service.ts +++ b/containers/ecr-viewer/src/app/api/conditions/service.ts @@ -1,65 +1,45 @@ -import { NextResponse } from "next/server"; -import sql from "mssql"; import { get_pool } from "../services/sqlserver_db"; import { getDB } from "../services/postgres_db"; /** * Retrieves all unique conditions from the ecr_rr_conditions table in the PostgreSQL database. - * @returns A promise resolving to a NextResponse object. - * @throws An error if the connection to the PostgreSQL database fails. + * @returns Array of conditions + * @throws An error if it fails to fetch data */ -export const get_conditions_postgres = async () => { +export const get_conditions_postgres = async (): Promise => { const { database, pgPromise } = getDB(); const { ParameterizedQuery: PQ } = pgPromise; try { - const response = await database.tx(async (t) => { - const getConditions = new PQ({ - text: 'SELECT DISTINCT "condition" FROM ecr_rr_conditions ORDER BY "condition"', - }); - - const conditions = await t.any(getConditions); - - return NextResponse.json( - conditions.map((c: any) => c.condition), - { status: 200 }, - ); + const getConditions = new PQ({ + text: 'SELECT DISTINCT "condition" FROM ecr_rr_conditions ORDER BY "condition"', }); + const conditions = await database.any<{ condition: string }>(getConditions); - return response; + return conditions.map((c) => c.condition); } catch (error: any) { - console.error("Error fetching data:", error); - return NextResponse.json({ message: error.message }, { status: 500 }); + console.error("Error fetching data: ", error); + throw Error("Error fetching data"); } }; /** * Retrieves all unique conditions from the ecr_rr_conditions table in the SQL Server database. - * @returns A promise resolving to a NextResponse object. - * @throws An error if the connection to the SQL Server database fails. + * @returns Array of conditions + * @throws An error if it fails to fetch data */ export const get_conditions_sqlserver = async () => { try { let pool = await get_pool(); if (!pool) { - return NextResponse.json( - { message: "Failed to connect to SQL Server." }, - { status: 500 }, - ); + throw Error("Failed to connnect to pool"); } - - const transaction = new sql.Transaction(pool); - await transaction.begin(); - const request = new sql.Request(transaction); - - const result = await request.query( - "SELECT DISTINCT erc.[condition] FROM ecr_rr_conditions erc ORDER BY erc.[condition]", - ); - const conditions: string[] = result.recordset.map((row) => row.condition); - - return NextResponse.json(conditions, { status: 200 }); + const result = await pool.request().query<{ + condition: string; + }>("SELECT DISTINCT condition FROM ecr_rr_conditions ORDER BY condition"); + return result.recordset.map((row) => row.condition); } catch (error: any) { - console.error("Error fetching data:", error); - return NextResponse.json({ message: error.message }, { status: 500 }); + console.error("Error fetching data: ", error); + throw Error("Error fetching data"); } }; diff --git a/containers/ecr-viewer/src/app/api/save-fhir-data/save-fhir-data-service.ts b/containers/ecr-viewer/src/app/api/save-fhir-data/save-fhir-data-service.ts index 241b99c03d..607d333457 100644 --- a/containers/ecr-viewer/src/app/api/save-fhir-data/save-fhir-data-service.ts +++ b/containers/ecr-viewer/src/app/api/save-fhir-data/save-fhir-data-service.ts @@ -233,10 +233,9 @@ export const saveMetadataToSqlServer = async ( return { message: "Failed to connect to SQL Server.", status: 500 }; } - const transaction = new sql.Transaction(pool); - await transaction.begin(); - if (process.env.METADATA_DATABASE_SCHEMA == "extended") { + const transaction = new sql.Transaction(pool); + await transaction.begin(); try { const ecrDataInsertRequest = new sql.Request(transaction); await ecrDataInsertRequest diff --git a/containers/ecr-viewer/src/app/api/tests/conditions.service.test.ts b/containers/ecr-viewer/src/app/api/tests/conditions.service.test.ts deleted file mode 100644 index 0ef9c91614..0000000000 --- a/containers/ecr-viewer/src/app/api/tests/conditions.service.test.ts +++ /dev/null @@ -1,206 +0,0 @@ -import { - get_conditions_postgres, - get_conditions_sqlserver, -} from "@/app/api/conditions/service"; -import { getDB } from "../services/postgres_db"; -import { NextResponse } from "next/server"; - -// Mock dependencies -jest.mock("mssql", () => ({ - connect: jest.fn(), - Transaction: jest.fn(), - Request: jest.fn(), -})); - -// Mock getDB and NextResponse -jest.mock("../services/postgres_db", () => ({ - getDB: jest.fn(), -})); - -jest.mock("next/server", () => ({ - NextResponse: { - json: jest.fn(), - }, -})); - -describe("get_conditions_postgres", () => { - const mockDatabase = { - tx: jest.fn(), - }; - - beforeEach(() => { - // Mock getDB to return the mock database - (getDB as jest.Mock).mockReturnValue({ - database: mockDatabase, - pgPromise: { - ParameterizedQuery: jest.fn().mockImplementation(({ text }: any) => ({ - text, - })), - }, - }); - }); - - afterEach(() => { - jest.clearAllMocks(); - }); - - it("should return conditions when database query succeeds", async () => { - const mockConditions = [ - { condition: "condition1" }, - { condition: "condition2" }, - ]; - - mockDatabase.tx.mockImplementation(async (callback: any) => { - const t = { - any: jest.fn().mockResolvedValue(mockConditions), - }; - return callback(t); - }); - - (NextResponse.json as jest.Mock).mockImplementation( - (data: any, options: any) => ({ data, options }), - ); - - const response = await get_conditions_postgres(); - - expect(response).toEqual({ - data: ["condition1", "condition2"], - options: { status: 200 }, - }); - - expect(NextResponse.json).toHaveBeenCalledWith( - ["condition1", "condition2"], - { status: 200 }, - ); - expect(mockDatabase.tx).toHaveBeenCalledTimes(1); - }); - - it("should return an error response when database query fails", async () => { - const errorMessage = "Database error"; - - // Mock database.tx to throw an error - mockDatabase.tx.mockImplementation(async () => { - throw new Error(errorMessage); - }); - - (NextResponse.json as jest.Mock).mockImplementation( - (data: any, options: any) => ({ data, options }), - ); - - const response = await get_conditions_postgres(); - - expect(response).toEqual({ - data: { message: errorMessage }, - options: { status: 500 }, - }); - - expect(NextResponse.json).toHaveBeenCalledWith( - { message: errorMessage }, - { status: 500 }, - ); - expect(mockDatabase.tx).toHaveBeenCalledTimes(1); - }); -}); - -describe("get_conditions_sqlserver", () => { - const sql = require("mssql"); - - afterEach(() => { - jest.clearAllMocks(); - }); - - it("should return conditions when database query succeeds", async () => { - const mockConditions = [ - { condition: "condition1" }, - { condition: "condition2" }, - ]; - - // Mock sql.connect - sql.connect.mockResolvedValue({}); - - // Mock Transaction - const mockTransaction = { - begin: jest.fn().mockResolvedValue(undefined), - commit: jest.fn().mockResolvedValue(undefined), - rollback: jest.fn().mockResolvedValue(undefined), - }; - sql.Transaction.mockImplementation(() => mockTransaction); - - // Mock Request - const mockRequest = { - query: jest.fn().mockResolvedValue({ recordset: mockConditions }), - }; - sql.Request.mockImplementation(() => mockRequest); - - // Mock NextResponse.json - (NextResponse.json as jest.Mock).mockImplementation( - (data: any, options: any) => { - return { data, options }; - }, - ); - - const response = await get_conditions_sqlserver(); - - expect(response).toEqual({ - data: ["condition1", "condition2"], - options: { status: 200 }, - }); - - expect(NextResponse.json).toHaveBeenCalledWith( - ["condition1", "condition2"], - { status: 200 }, - ); - - // Verify that the SQL methods were called as expected - expect(sql.connect).toHaveBeenCalled(); - expect(mockTransaction.begin).toHaveBeenCalled(); - expect(mockRequest.query).toHaveBeenCalledWith( - "SELECT DISTINCT erc.[condition] FROM ecr_rr_conditions erc ORDER BY erc.[condition]", - ); - }); - - it("should handle error when database query fails", async () => { - const errorMessage = "Database error"; - - // Mock sql.connect - sql.connect.mockResolvedValue({}); - - // Mock Transaction that will throw an error - const mockTransaction = { - begin: jest.fn().mockResolvedValue(undefined), - }; - sql.Transaction.mockImplementation(() => mockTransaction); - - // Mock Request that throws an error - const mockRequest = { - query: jest.fn().mockRejectedValue(new Error(errorMessage)), - }; - sql.Request.mockImplementation(() => mockRequest); - - // Mock NextResponse.json - (NextResponse.json as jest.Mock).mockImplementation( - (data: any, options: any) => { - return { data, options }; - }, - ); - - const response = await get_conditions_sqlserver(); - - expect(response).toEqual({ - data: { message: errorMessage }, - options: { status: 500 }, - }); - - expect(NextResponse.json).toHaveBeenCalledWith( - { message: errorMessage }, - { status: 500 }, - ); - - // Verify that the SQL methods were called as expected - expect(sql.connect).toHaveBeenCalled(); - expect(mockTransaction.begin).toHaveBeenCalled(); - expect(mockRequest.query).toHaveBeenCalledWith( - "SELECT DISTINCT erc.[condition] FROM ecr_rr_conditions erc ORDER BY erc.[condition]", - ); - }); -}); diff --git a/containers/ecr-viewer/src/app/tests/api/conditions/route.test.ts b/containers/ecr-viewer/src/app/tests/api/conditions/route.test.ts new file mode 100644 index 0000000000..e7370bb22b --- /dev/null +++ b/containers/ecr-viewer/src/app/tests/api/conditions/route.test.ts @@ -0,0 +1,72 @@ +/** + * @jest-environment node + */ +import { GET } from "@/app/api/conditions/route"; +import { + get_conditions_postgres, + get_conditions_sqlserver, +} from "@/app/api/conditions/service"; + +jest.mock("../../../api/conditions/service", () => ({ + get_conditions_postgres: jest.fn(), + get_conditions_sqlserver: jest.fn(), +})); + +describe("GET Conditions", () => { + afterEach(() => { + delete process.env.METADATA_DATABASE_TYPE; + jest.resetAllMocks(); + }); + + it("should return a 200 response with postgres conditions when metadataSaveLocation is postgres", async () => { + process.env.METADATA_DATABASE_TYPE = "postgres"; + const mockConditions = ["Condition X", "Condition Y"]; + (get_conditions_postgres as jest.Mock).mockResolvedValue(mockConditions); + + const response = await GET(); + + expect(get_conditions_postgres).toHaveBeenCalledOnce(); + expect(get_conditions_sqlserver).not.toHaveBeenCalled(); + expect(response.status).toEqual(200); + expect(await response.json()).toEqual(mockConditions); + }); + + it("should return a 200 response with sqlserver conditions when metadataSaveLocation is sqlserver", async () => { + process.env.METADATA_DATABASE_TYPE = "sqlserver"; + const mockConditions = ["Condition A", "Condition B"]; + (get_conditions_sqlserver as jest.Mock).mockResolvedValue(mockConditions); + + const response = await GET(); + + expect(get_conditions_sqlserver).toHaveBeenCalledOnce(); + expect(get_conditions_postgres).not.toHaveBeenCalled(); + expect(response.status).toEqual(200); + expect(await response.json()).toEqual(mockConditions); + }); + + it("should return a 500 response when METADATA_DATABASE_TYPE is invalid", async () => { + delete process.env.METADATA_DATABASE_TYPE; + + const response = await GET(); + + expect(response.status).toEqual(500); + expect(await response.json()).toEqual({ + message: "Invalid metadata location.", + }); + }); + + it("should return a 500 response when an error occurs", async () => { + process.env.METADATA_DATABASE_TYPE = "postgres"; + const mockError = new Error("Test error"); + (get_conditions_postgres as jest.Mock).mockRejectedValue(mockError); + + const response = await GET(); + + expect(get_conditions_postgres).toHaveBeenCalled(); + expect(get_conditions_sqlserver).not.toHaveBeenCalled(); + expect(response.status).toEqual(500); + expect(await response.json()).toEqual({ + message: "Failed to get conditions.", + }); + }); +}); diff --git a/containers/ecr-viewer/src/app/tests/api/conditions/service.test.ts b/containers/ecr-viewer/src/app/tests/api/conditions/service.test.ts new file mode 100644 index 0000000000..537785642a --- /dev/null +++ b/containers/ecr-viewer/src/app/tests/api/conditions/service.test.ts @@ -0,0 +1,97 @@ +/** + * @jest-environment node + */ +import { + get_conditions_postgres, + get_conditions_sqlserver, +} from "@/app/api/conditions/service"; +import { getDB } from "@/app/api/services/postgres_db"; +import { get_pool } from "@/app/api/services/sqlserver_db"; + +jest.mock("../../../api/services/postgres_db", () => ({ + getDB: jest.fn(), +})); + +jest.mock("../../../api/services/sqlserver_db", () => ({ + get_pool: jest.fn(), +})); + +describe("get_conditions_postgres", () => { + const mockDatabase = { + any: jest.fn(), + }; + + beforeEach(() => { + // Mock getDB to return the mock database + (getDB as jest.Mock).mockReturnValue({ + database: mockDatabase, + pgPromise: { + ParameterizedQuery: jest.fn().mockImplementation(({ text }: any) => ({ + text, + })), + }, + }); + }); + + afterEach(() => { + jest.resetAllMocks(); + }); + + it("should return conditions when database query succeeds", async () => { + const mockConditions = [ + { condition: "condition1" }, + { condition: "condition2" }, + ]; + mockDatabase.any.mockReturnValue(mockConditions); + const response = await get_conditions_postgres(); + + expect(response).toEqual(["condition1", "condition2"]); + expect(mockDatabase.any).toHaveBeenCalledTimes(1); + }); + + it("should return an error response when database query fails", async () => { + jest.spyOn(console, "error").mockImplementation(() => {}); + + const errorMessage = "Database error"; + + mockDatabase.any.mockImplementation(async () => { + throw new Error(errorMessage); + }); + + await expect(get_conditions_postgres()).rejects.toThrow( + "Error fetching data", + ); + }); +}); + +describe("get_conditions_sqlserver", () => { + afterEach(() => { + jest.resetAllMocks(); + }); + + it("should return conditions when database query succeeds", async () => { + const mockConditions = [ + { condition: "condition1" }, + { condition: "condition2" }, + ]; + + const mockRequest = { + query: jest.fn().mockResolvedValue({ recordset: mockConditions }), + }; + (get_pool as jest.Mock).mockReturnValue({ request: () => mockRequest }); + + const response = await get_conditions_sqlserver(); + + expect(response).toEqual(["condition1", "condition2"]); + expect(mockRequest.query).toHaveBeenCalledWith( + "SELECT DISTINCT condition FROM ecr_rr_conditions ORDER BY condition", + ); + }); + + it("should handle error when database query fails", async () => { + jest.spyOn(console, "error").mockImplementation(() => {}); + await expect(get_conditions_sqlserver()).rejects.toThrow( + "Error fetching data", + ); + }); +});