feat(MED-161): add jest for service tests

This commit is contained in:
2025-09-17 11:17:50 +03:00
parent 64acdfcbbb
commit 4302ddb90e
8 changed files with 1503 additions and 344 deletions

44
__mocks__/isikukood.ts Normal file
View File

@@ -0,0 +1,44 @@
// Mock for isikukood library to avoid ES module issues in tests
export enum Gender {
MALE = 'male',
FEMALE = 'female',
}
export default class Isikukood {
private code: string;
constructor(code: string) {
this.code = code;
}
static validate(code: string): boolean {
return true; // Mock always returns true for tests
}
static generate(options?: { gender?: Gender; century?: number }): string {
return '39001010002'; // Mock Estonian ID code
}
isValid(): boolean {
return true;
}
getGender(): Gender {
return Gender.MALE;
}
getBirthDate(): Date {
return new Date('1990-01-01');
}
getAge(): number {
return 30;
}
getCentury(): number {
return 3;
}
}
export { Isikukood };

3
__mocks__/server-only.ts Normal file
View File

@@ -0,0 +1,3 @@
// Mock for server-only to avoid Next.js server component issues in tests
// This module does nothing in tests - it's just a marker for Next.js
export {};

View File

@@ -833,282 +833,6 @@ const big2: AnalysisTestResponse = {
],
};
const big2: AnalysisTestResponse = {
"id": 3,
"orderedAnalysisElements": [
{
"analysisIdOriginal": "57021-8",
"isWaitingForResults": false,
"analysisName": "Hemogramm",
"results": {
"nestedElements": [
{
"status": 4,
"unit": "g/L",
"normLower": 134,
"normUpper": 170,
"normStatus": 0,
"responseTime": "2025-09-12 14:02:03",
"responseValue": 150,
"normLowerIncluded": false,
"normUpperIncluded": false,
"analysisElementOriginalId": "718-7"
},
{
"status": 4,
"unit": "%",
"normLower": 40,
"normUpper": 49,
"normStatus": 0,
"responseTime": "2025-09-12 14:02:03",
"responseValue": 45,
"normLowerIncluded": false,
"normUpperIncluded": false,
"analysisElementOriginalId": "4544-3"
},
{
"status": 4,
"unit": "E9/L",
"normLower": 4.1,
"normUpper": 9.7,
"normStatus": 0,
"responseTime": "2025-09-12 14:02:03",
"responseValue": 5,
"normLowerIncluded": false,
"normUpperIncluded": false,
"analysisElementOriginalId": "6690-2"
},
{
"status": 4,
"unit": "E12/L",
"normLower": 4.5,
"normUpper": 5.7,
"normStatus": 0,
"responseTime": "2025-09-12 14:02:03",
"responseValue": 5,
"normLowerIncluded": false,
"normUpperIncluded": false,
"analysisElementOriginalId": "789-8"
},
{
"status": 4,
"unit": "fL",
"normLower": 82,
"normUpper": 95,
"normStatus": 0,
"responseTime": "2025-09-12 14:02:04",
"responseValue": 85,
"normLowerIncluded": false,
"normUpperIncluded": false,
"analysisElementOriginalId": "787-2"
},
{
"status": 4,
"unit": "pg",
"normLower": 28,
"normUpper": 33,
"normStatus": 0,
"responseTime": "2025-09-12 14:02:04",
"responseValue": 30,
"normLowerIncluded": false,
"normUpperIncluded": false,
"analysisElementOriginalId": "785-6"
},
{
"status": 4,
"unit": "g/L",
"normLower": 322,
"normUpper": 356,
"normStatus": 0,
"responseTime": "2025-09-12 14:02:04",
"responseValue": 355,
"normLowerIncluded": false,
"normUpperIncluded": false,
"analysisElementOriginalId": "786-4"
},
{
"status": 4,
"unit": "%",
"normLower": 12,
"normUpper": 15,
"normStatus": 0,
"responseTime": "2025-09-12 14:02:04",
"responseValue": 15,
"normLowerIncluded": false,
"normUpperIncluded": false,
"analysisElementOriginalId": "788-0"
},
{
"status": 4,
"unit": "E9/L",
"normLower": 157,
"normUpper": 372,
"normStatus": 0,
"responseTime": "2025-09-12 14:02:04",
"responseValue": 255,
"normLowerIncluded": false,
"normUpperIncluded": false,
"analysisElementOriginalId": "777-3"
},
{
"status": 4,
"unit": "%",
"normLower": 0.18,
"normUpper": 0.38,
"normStatus": 0,
"responseTime": "2025-09-12 14:02:04",
"responseValue": 0.2,
"normLowerIncluded": false,
"normUpperIncluded": false,
"analysisElementOriginalId": "51637-7"
},
{
"status": 4,
"unit": "fL",
"normLower": 9.2,
"normUpper": 12.3,
"normStatus": 0,
"responseTime": "2025-09-12 14:02:04",
"responseValue": 10,
"normLowerIncluded": false,
"normUpperIncluded": false,
"analysisElementOriginalId": "32623-1"
},
{
"status": 4,
"unit": "fL",
"normLower": 10.1,
"normUpper": 16.2,
"normStatus": 0,
"responseTime": "2025-09-12 14:02:04",
"responseValue": 15,
"normLowerIncluded": false,
"normUpperIncluded": false,
"analysisElementOriginalId": "32207-3"
},
{
"status": 4,
"unit": "E9/L",
"normLower": 0.01,
"normUpper": 0.08,
"normStatus": 0,
"responseTime": "2025-09-12 14:02:04",
"responseValue": 0.05,
"normLowerIncluded": false,
"normUpperIncluded": false,
"analysisElementOriginalId": "704-7"
},
{
"status": 4,
"unit": "E9/L",
"normLower": 0.02,
"normUpper": 0.4,
"normStatus": 0,
"responseTime": "2025-09-12 14:02:04",
"responseValue": 0.05,
"normLowerIncluded": false,
"normUpperIncluded": false,
"analysisElementOriginalId": "711-2"
},
{
"status": 4,
"unit": "E9/L",
"normLower": 1.9,
"normUpper": 6.7,
"normStatus": 0,
"responseTime": "2025-09-12 14:02:04",
"responseValue": 5,
"normLowerIncluded": false,
"normUpperIncluded": false,
"analysisElementOriginalId": "751-8"
},
{
"status": 4,
"unit": "E9/L",
"normLower": 0.24,
"normUpper": 0.8,
"normStatus": 0,
"responseTime": "2025-09-12 14:02:04",
"responseValue": 0.5,
"normLowerIncluded": false,
"normUpperIncluded": false,
"analysisElementOriginalId": "742-7"
},
{
"status": 4,
"unit": "E9/L",
"normLower": 1.3,
"normUpper": 3.1,
"normStatus": 0,
"responseTime": "2025-09-12 14:02:04",
"responseValue": 1.5,
"normLowerIncluded": false,
"normUpperIncluded": false,
"analysisElementOriginalId": "731-0"
},
{
"status": 4,
"unit": "E9/L",
"normLower": 0,
"normUpper": 0.03,
"normStatus": 0,
"responseTime": "2025-09-12 14:02:03",
"responseValue": 0,
"normLowerIncluded": false,
"normUpperIncluded": false,
"analysisElementOriginalId": "51584-1"
},
{
"status": 4,
"unit": "%",
"normLower": 0,
"normUpper": 0.5,
"normStatus": 0,
"responseTime": "2025-09-12 14:02:04",
"responseValue": 0,
"normLowerIncluded": false,
"normUpperIncluded": false,
"analysisElementOriginalId": "38518-7"
},
{
"status": 4,
"unit": "E9/L",
"normStatus": 0,
"responseTime": "2025-09-12 14:02:04",
"responseValue": 0,
"normLowerIncluded": false,
"normUpperIncluded": false,
"analysisElementOriginalId": "771-6"
},
{
"status": 4,
"unit": "/100WBC",
"normStatus": 0,
"responseTime": "2025-09-12 14:02:04",
"responseValue": 0,
"normLowerIncluded": false,
"normUpperIncluded": false,
"analysisElementOriginalId": "58413-6"
}
],
"unit": null,
"normLower": null,
"normUpper": null,
"normStatus": null,
"responseTime": null,
"responseValue": null,
"normLowerIncluded": false,
"normUpperIncluded": false,
"responseValueIsNegative": false,
"responseValueIsWithinNorm": false,
"status": "4",
"analysisElementOriginalId": "57021-8",
"summary": 'test'
},
},
],
};
export const analysisResponses: AnalysisTestResponse[] = [
empty1,
big1,

66
jest.config.js Normal file
View File

@@ -0,0 +1,66 @@
import { pathsToModuleNameMapper } from 'ts-jest';
import { readFileSync } from 'fs';
const tsconfig = JSON.parse(readFileSync('./tsconfig.json', 'utf8'));
/** @type {import('jest').Config} */
export default {
preset: 'ts-jest',
testEnvironment: 'node',
// Handle module resolution for TypeScript paths
moduleNameMapper: {
...pathsToModuleNameMapper(tsconfig.compilerOptions.paths, {
prefix: '<rootDir>/',
}),
// Mock problematic libraries
'^isikukood$': '<rootDir>/__mocks__/isikukood.ts',
'^server-only$': '<rootDir>/__mocks__/server-only.ts',
},
// Test file patterns
testMatch: [
'**/__tests__/**/*.(ts|tsx|js)',
'**/*.(test|spec).(ts|tsx|js)'
],
// Setup files
setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
// Coverage configuration
collectCoverageFrom: [
'lib/**/*.{ts,tsx}',
'app/**/*.{ts,tsx}',
'components/**/*.{ts,tsx}',
'!**/*.d.ts',
'!**/node_modules/**',
'!**/.next/**'
],
// Transform configuration
transform: {
'^.+\\.(ts|tsx)$': ['ts-jest', {
useESM: true,
tsconfig: {
jsx: 'react-jsx',
},
}],
},
// Module file extensions
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json'],
// Ignore patterns
testPathIgnorePatterns: [
'<rootDir>/.next/',
'<rootDir>/node_modules/',
],
// Transform ignore patterns for node_modules
transformIgnorePatterns: [
'node_modules/(?!(.*\\.mjs$))',
],
// ESM support
extensionsToTreatAsEsm: ['.ts'],
};

59
jest.setup.js Normal file
View File

@@ -0,0 +1,59 @@
// Jest setup file for global test configuration
// Mock Next.js router
jest.mock('next/router', () => ({
useRouter() {
return {
route: '/',
pathname: '/',
query: {},
asPath: '/',
push: jest.fn(),
pop: jest.fn(),
reload: jest.fn(),
back: jest.fn(),
prefetch: jest.fn().mockResolvedValue(undefined),
beforePopState: jest.fn(),
events: {
on: jest.fn(),
off: jest.fn(),
emit: jest.fn(),
},
isFallback: false,
};
},
}));
// Mock Next.js navigation
jest.mock('next/navigation', () => ({
useRouter() {
return {
push: jest.fn(),
replace: jest.fn(),
prefetch: jest.fn(),
back: jest.fn(),
forward: jest.fn(),
refresh: jest.fn(),
};
},
useSearchParams() {
return new URLSearchParams();
},
usePathname() {
return '/';
},
}));
// Global test utilities
global.console = {
...console,
// Suppress console.log in tests unless needed
log: jest.fn(),
debug: jest.fn(),
info: jest.fn(),
warn: jest.fn(),
error: jest.fn(),
};
// Set up environment variables for tests
process.env.NODE_ENV = 'test';

View File

@@ -0,0 +1,112 @@
import { AnalysisResponseElement } from "~/lib/types/analysis-response-element";
import { canCreateAnalysisResponseElement, getAnalysisResponseElementsForGroup } from "./medipostPrivateMessage.service";
import { ResponseUuring } from "@/packages/shared/src/types/medipost-analysis";
type TestExistingElement = Pick<AnalysisResponseElement, 'analysis_element_original_id' | 'status' | 'response_value'>;
describe('medipostPrivateMessage.service', () => {
describe('canCreateAnalysisResponseElement', () => {
it('should return true if the analysis response element does not exist', async () => {
const existingElements = [] as TestExistingElement[];
const groupUuring = {
UuringuElement: {
UuringOlek: 1,
UuringId: '1',
},
} as const;
const responseValue = 1;
const log = jest.fn();
expect(await canCreateAnalysisResponseElement({ existingElements, groupUuring, responseValue, log })).toBe(true);
});
it('should return false if the analysis response element exists and the status is higher', async () => {
const existingElements = [{ analysis_element_original_id: '1', status: '2', response_value: 1 }] as TestExistingElement[];
const groupUuring = {
UuringuElement: {
UuringOlek: 1,
UuringId: '1',
},
} as const;
const responseValue = 1;
const log = jest.fn();
expect(await canCreateAnalysisResponseElement({ existingElements, groupUuring, responseValue, log })).toBe(false);
});
});
describe('getAnalysisResponseElementsForGroup', () => {
it('should return single new element', async () => {
const analysisGroup = {
UuringuGruppNimi: '1',
Uuring: [
{
UuringuElement: {
UuringOlek: 1,
UuringId: '1',
UuringuVastus: [{ VastuseVaartus: '1' }],
},
},
] as unknown as ResponseUuring[],
} as const;
const existingElements = [] as TestExistingElement[];
const log = jest.fn();
expect(await getAnalysisResponseElementsForGroup({ analysisGroup, existingElements, log }))
.toEqual([{
analysis_element_original_id: '1',
analysis_name: undefined,
comment: null,
norm_lower: null,
norm_lower_included: false,
norm_status: undefined,
norm_upper: null,
norm_upper_included: false,
response_time: null,
response_value: 1,
unit: null,
original_response_element: {
UuringOlek: 1,
UuringId: '1',
UuringuVastus: [{ VastuseVaartus: '1' }],
},
status: '1',
}]);
});
it('should return no new element if element already exists in higher status', async () => {
const analysisGroup = {
UuringuGruppNimi: '1',
Uuring: [
{
UuringuElement: {
UuringOlek: 1,
UuringId: '1',
UuringuVastus: [{ VastuseVaartus: '1' }],
},
},
] as unknown as ResponseUuring[],
} as const;
const existingElements = [{ analysis_element_original_id: '1', status: '2', response_value: 1 }] as TestExistingElement[];
const log = jest.fn();
expect(await getAnalysisResponseElementsForGroup({ analysisGroup, existingElements, log }))
.toEqual([]);
});
it('should return no new element if element already exists with response value', async () => {
const analysisGroup = {
UuringuGruppNimi: '1',
Uuring: [
{
UuringuElement: {
UuringOlek: 1,
UuringId: '1',
UuringuVastus: [{ VastuseVaartus: '' }],
},
},
] as unknown as ResponseUuring[],
} as const;
const existingElements = [{ analysis_element_original_id: '1', status: '1', response_value: 1 }] as TestExistingElement[];
const log = jest.fn();
expect(await getAnalysisResponseElementsForGroup({ analysisGroup, existingElements, log }))
.toEqual([]);
});
});
});

View File

@@ -16,6 +16,10 @@
"start": "next start",
"start:test": "NODE_ENV=test next start",
"typecheck": "tsc --noEmit",
"test": "jest",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage",
"test:ci": "jest --ci --coverage --watchAll=false",
"supabase": "supabase",
"supabase:start": "supabase status || supabase start",
"supabase:stop": "supabase stop",
@@ -88,6 +92,7 @@
},
"devDependencies": {
"@hookform/resolvers": "^5.0.1",
"@jest/globals": "^30.1.2",
"@kit/eslint-config": "workspace:*",
"@kit/prettier-config": "workspace:*",
"@kit/tsconfig": "workspace:*",
@@ -95,6 +100,7 @@
"@medusajs/ui-preset": "latest",
"@next/bundle-analyzer": "15.3.2",
"@tailwindcss/postcss": "^4.1.10",
"@types/jest": "^30.0.0",
"@types/jsonwebtoken": "9.0.10",
"@types/lodash": "^4.17.17",
"@types/node": "^22.15.32",
@@ -103,11 +109,14 @@
"babel-plugin-react-compiler": "19.1.0-rc.2",
"cssnano": "^7.0.7",
"dotenv": "^16.5.0",
"jest": "^30.1.3",
"jest-environment-node": "^30.1.2",
"pino-pretty": "13.0.0",
"prettier": "^3.5.3",
"supabase": "^2.30.4",
"tailwindcss": "4.1.7",
"tailwindcss-animate": "^1.0.7",
"ts-jest": "^29.4.2",
"typescript": "^5.8.3",
"yup": "^1.6.1"
},

1278
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff