Bug 40447: Add comprehensive JSDoc documentation to Cypress plugins

This patch adds detailed JSDoc documentation to all Cypress testing
plugin files.

Documentation added to:
- insertData.js: Data insertion utilities with examples
- api-client.js: API wrapper functions with usage patterns
- mockData.js: Schema-based mock data generation
- auth.js: Authentication helper functions
- db.js: Database query utilities
- readYamlFile.js: YAML file reading utilities
- index.js: Main plugin configuration

Each function now includes:
- Complete parameter descriptions with types
- Return value documentation
- Practical usage examples
- Error conditions and handling
- Module-level overviews

This addresses the documentation gap identified during QA review,
hopefully making it easier for future devs to adopt the new framework of
tools.

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
This commit is contained in:
Martin Renvoize 2025-07-18 11:52:08 +01:00 committed by Lucas Gass
parent 3f6a2cb872
commit d9122cd5ac
No known key found for this signature in database
GPG Key ID: 60B447398B0D36CA
7 changed files with 600 additions and 0 deletions

View File

@ -1,7 +1,33 @@
/**
* Koha API Client for Cypress Testing
*
* This module provides a wrapper around the Koha API client for use in Cypress tests.
* It handles authentication, request preparation, and provides convenient methods
* for making API calls during test execution.
*
* @module api-client
*/
const { APIClient } = require("./dist/api-client.cjs.js");
const client = APIClient.default.koha;
/**
* Prepares request parameters for API calls by extracting and organizing headers and URL.
*
* @function prepareRequest
* @param {Object} params - Request parameters
* @param {string} params.baseUrl - Base URL for the API
* @param {string} params.endpoint - API endpoint path
* @param {string} [params.authHeader] - Authorization header value
* @param {Object} [params.headers={}] - Additional headers to include
* @param {...*} params.rest - Other parameters to pass through
* @returns {Object} Prepared request object
* @returns {string} returns.url - Complete URL for the request
* @returns {Object} returns.headers - Combined headers object
* @returns {Object} returns.rest - Pass-through parameters
* @private
*/
const prepareRequest = params => {
const { baseUrl, endpoint, authHeader, headers = {}, ...rest } = params;
const url = baseUrl + endpoint;
@ -12,6 +38,33 @@ const prepareRequest = params => {
return { url, headers: finalHeaders, rest };
};
/**
* Performs a GET request to the Koha API.
*
* @function apiGet
* @param {Object} params - Request parameters
* @param {string} params.baseUrl - Base URL for the API
* @param {string} params.endpoint - API endpoint path
* @param {string} [params.authHeader] - Authorization header value
* @param {Object} [params.headers={}] - Additional headers to include
* @param {...*} params.rest - Additional parameters for the request
* @returns {Promise<*>} API response data
* @example
* // Get a list of patrons
* const patrons = await apiGet({
* baseUrl: 'http://localhost:8081',
* endpoint: '/api/v1/patrons',
* authHeader: 'Basic dGVzdDp0ZXN0'
* });
*
* @example
* // Get a specific patron with query parameters
* const patron = await apiGet({
* baseUrl: 'http://localhost:8081',
* endpoint: '/api/v1/patrons?q={"patron_id":123}',
* authHeader: 'Basic dGVzdDp0ZXN0'
* });
*/
const apiGet = params => {
const { url, headers, rest } = prepareRequest(params);
return client.get({
@ -21,6 +74,32 @@ const apiGet = params => {
});
};
/**
* Performs a POST request to the Koha API.
*
* @function apiPost
* @param {Object} params - Request parameters
* @param {string} params.baseUrl - Base URL for the API
* @param {string} params.endpoint - API endpoint path
* @param {string} [params.authHeader] - Authorization header value
* @param {Object} [params.headers={}] - Additional headers to include
* @param {Object} [params.body] - Request body data
* @param {...*} params.rest - Additional parameters for the request
* @returns {Promise<*>} API response data
* @example
* // Create a new patron
* const newPatron = await apiPost({
* baseUrl: 'http://localhost:8081',
* endpoint: '/api/v1/patrons',
* authHeader: 'Basic dGVzdDp0ZXN0',
* body: {
* firstname: 'John',
* surname: 'Doe',
* library_id: 'CPL',
* category_id: 'PT'
* }
* });
*/
const apiPost = params => {
const { url, headers, rest } = prepareRequest(params);
return client.post({
@ -30,6 +109,29 @@ const apiPost = params => {
});
};
/**
* Performs a PUT request to the Koha API.
*
* @function apiPut
* @param {Object} params - Request parameters
* @param {string} params.baseUrl - Base URL for the API
* @param {string} params.endpoint - API endpoint path
* @param {string} [params.authHeader] - Authorization header value
* @param {Object} [params.headers={}] - Additional headers to include
* @param {Object} [params.body] - Request body data
* @param {...*} params.rest - Additional parameters for the request
* @returns {Promise<*>} API response data
* @example
* // Update a patron
* const updatedPatron = await apiPut({
* baseUrl: 'http://localhost:8081',
* endpoint: '/api/v1/patrons/123',
* authHeader: 'Basic dGVzdDp0ZXN0',
* body: {
* email: 'newemail@example.com'
* }
* });
*/
const apiPut = params => {
const { url, headers, rest } = prepareRequest(params);
return client.put({
@ -39,6 +141,25 @@ const apiPut = params => {
});
};
/**
* Performs a DELETE request to the Koha API.
*
* @function apiDelete
* @param {Object} params - Request parameters
* @param {string} params.baseUrl - Base URL for the API
* @param {string} params.endpoint - API endpoint path
* @param {string} [params.authHeader] - Authorization header value
* @param {Object} [params.headers={}] - Additional headers to include
* @param {...*} params.rest - Additional parameters for the request
* @returns {Promise<*>} API response data
* @example
* // Delete a patron
* await apiDelete({
* baseUrl: 'http://localhost:8081',
* endpoint: '/api/v1/patrons/123',
* authHeader: 'Basic dGVzdDp0ZXN0'
* });
*/
const apiDelete = params => {
const { url, headers, rest } = prepareRequest(params);
return client.delete({

View File

@ -1,5 +1,33 @@
/**
* Authentication utilities for Cypress testing
*
* This module provides authentication helper functions for use in Cypress tests
* when making API calls that require authentication.
*
* @module auth
*/
const { Buffer } = require("buffer");
/**
* Generates a Basic Authentication header from username and password.
*
* @function getBasicAuthHeader
* @param {string} username - Username for authentication
* @param {string} password - Password for authentication
* @returns {string} Basic authentication header value in format "Basic <base64>"
* @example
* // Generate auth header for API calls
* const authHeader = getBasicAuthHeader('koha', 'koha');
* // Returns: "Basic a29oYTprb2hh"
*
* // Use with API client
* const response = await apiGet({
* baseUrl: 'http://localhost:8081',
* endpoint: '/api/v1/patrons',
* authHeader: getBasicAuthHeader('koha', 'koha')
* });
*/
const getBasicAuthHeader = (username, password) => {
const credentials = Buffer.from(`${username}:${password}`).toString(
"base64"

View File

@ -1,5 +1,21 @@
/**
* Database Query Utilities for Cypress Testing
*
* This module provides direct database access for Cypress tests when API
* endpoints are not available or when direct database operations are needed
* for test setup and cleanup.
*
* @module db
*/
const mysql = require("mysql2/promise");
/**
* Database connection configuration
*
* @todo Replace hardcoded credentials with environment variables
* @type {Object}
*/
const connectionConfig = {
host: "db",
user: "koha_kohadev",
@ -7,6 +23,39 @@ const connectionConfig = {
database: "koha_kohadev",
};
/**
* Executes a SQL query with optional parameters.
*
* @async
* @function query
* @param {string} sql - SQL query string with optional parameter placeholders (?)
* @param {Array} [params=[]] - Array of parameter values for the query
* @returns {Promise<Array>} Query results as an array of rows
* @throws {Error} When database connection or query execution fails
* @description This function:
* - Creates a new database connection for each query
* - Uses parameterized queries to prevent SQL injection
* - Automatically closes the connection after execution
* - Returns the raw result rows from the database
*
* @example
* // Simple SELECT query
* const patrons = await query('SELECT * FROM borrowers LIMIT 10');
*
* @example
* // Parameterized query for safety
* const patron = await query(
* 'SELECT * FROM borrowers WHERE borrowernumber = ?',
* [123]
* );
*
* @example
* // DELETE query with multiple parameters
* await query(
* 'DELETE FROM issues WHERE issue_id IN (?, ?, ?)',
* [1, 2, 3]
* );
*/
async function query(sql, params = []) {
const connection = await mysql.createConnection(connectionConfig);
const [rows] = await connection.execute(sql, params);

View File

@ -1,3 +1,14 @@
/**
* Cypress Plugin Configuration
*
* This is the main Cypress plugin configuration file that registers all
* testing utilities as Cypress tasks. It provides a bridge between Cypress
* tests and the various utility modules for data generation, API access,
* and database operations.
*
* @module cypress-plugins
*/
const { startDevServer } = require("@cypress/webpack-dev-server");
const { buildSampleObject, buildSampleObjects } = require("./mockData.js");
@ -17,6 +28,43 @@ const { query } = require("./db.js");
const { apiGet, apiPost, apiPut, apiDelete } = require("./api-client.js");
/**
* Cypress plugin configuration function.
*
* @function
* @param {Function} on - Cypress plugin registration function
* @param {Object} config - Cypress configuration object
* @param {string} config.baseUrl - Base URL for the application under test
* @param {Object} config.env - Environment variables from cypress.config.js
* @param {string} config.env.apiUsername - Username for API authentication
* @param {string} config.env.apiPassword - Password for API authentication
* @returns {Object} Modified Cypress configuration
* @description This function:
* - Registers all testing utilities as Cypress tasks
* - Sets up authentication headers for API calls
* - Configures the development server for component testing
* - Provides automatic parameter injection for common arguments
*
* Available Cypress tasks:
* - Data Generation: buildSampleObject, buildSampleObjects
* - Data Insertion: insertSampleBiblio, insertSampleHold, insertSampleCheckout, insertSamplePatron
* - Data Cleanup: deleteSampleObjects
* - API Access: apiGet, apiPost, apiPut, apiDelete
* - Database Access: query
* - Authentication: getBasicAuthHeader
*
* @example
* // Usage in Cypress tests
* cy.task('insertSampleBiblio', { item_count: 2 }).then(result => {
* // Test with the created biblio
* });
*
* @example
* // API call through task
* cy.task('apiGet', { endpoint: '/api/v1/patrons' }).then(patrons => {
* // Work with patron data
* });
*/
module.exports = (on, config) => {
const baseUrl = config.baseUrl;
const authHeader = getBasicAuthHeader(

View File

@ -1,8 +1,51 @@
/**
* Koha Cypress Testing Data Insertion Utilities
*
* This module provides functions to create and manage test data for Cypress tests.
* It handles creating complete bibliographic records, patrons, holds, checkouts,
* and other Koha objects with proper relationships and dependencies.
*
* @module insertData
*/
const { buildSampleObject, buildSampleObjects } = require("./mockData.js");
const { query } = require("./db.js");
const { apiGet, apiPost } = require("./api-client.js");
/**
* Creates a complete bibliographic record with associated items and libraries.
*
* @async
* @function insertSampleBiblio
* @param {Object} params - Configuration parameters
* @param {number} params.item_count - Number of items to create for this biblio
* @param {Object} [params.options] - Additional options
* @param {boolean} [params.options.different_libraries] - If true, creates different libraries for each item
* @param {string} params.baseUrl - Base URL for API calls
* @param {string} params.authHeader - Authorization header for API calls
* @returns {Promise<Object>} Created biblio with items, libraries, and item_type
* @returns {Object} returns.biblio - The created bibliographic record
* @returns {Array<Object>} returns.items - Array of created item records
* @returns {Array<Object>} returns.libraries - Array of created library records
* @returns {Object} returns.item_type - The created item type record
* @example
* // Create a biblio with 3 items using the same library
* const result = await insertSampleBiblio({
* item_count: 3,
* baseUrl: 'http://localhost:8081',
* authHeader: 'Basic dGVzdDp0ZXN0'
* });
*
* @example
* // Create a biblio with 2 items using different libraries
* const result = await insertSampleBiblio({
* item_count: 2,
* options: { different_libraries: true },
* baseUrl: 'http://localhost:8081',
* authHeader: 'Basic dGVzdDp0ZXN0'
* });
*/
const insertSampleBiblio = async ({
item_count,
options,
@ -160,6 +203,39 @@ const insertSampleBiblio = async ({
return { biblio, items: createdItems, libraries, item_type };
};
/**
* Creates a hold request for a bibliographic record or item.
*
* @async
* @function insertSampleHold
* @param {Object} params - Configuration parameters
* @param {Object} [params.item] - Item to place hold on (optional if biblio provided)
* @param {Object} [params.biblio] - Biblio to place hold on (optional if item provided)
* @param {string} [params.library_id] - Library ID for pickup location (defaults to item's home library)
* @param {string} params.baseUrl - Base URL for API calls
* @param {string} params.authHeader - Authorization header for API calls
* @returns {Promise<Object>} Created hold with associated patron and patron_category
* @returns {Object} returns.hold - The created hold record
* @returns {Object} returns.patron - The patron who placed the hold
* @returns {Object} returns.patron_category - The patron's category
* @throws {Error} When neither library_id nor item is provided
* @example
* // Create a hold on a specific item
* const holdResult = await insertSampleHold({
* item: { item_id: 123, home_library_id: 'CPL' },
* baseUrl: 'http://localhost:8081',
* authHeader: 'Basic dGVzdDp0ZXN0'
* });
*
* @example
* // Create a biblio-level hold
* const holdResult = await insertSampleHold({
* biblio: { biblio_id: 456 },
* library_id: 'CPL',
* baseUrl: 'http://localhost:8081',
* authHeader: 'Basic dGVzdDp0ZXN0'
* });
*/
const insertSampleHold = async ({
item,
biblio,
@ -199,6 +275,38 @@ const insertSampleHold = async ({
return { hold, patron, patron_category };
};
/**
* Creates a checkout record with associated biblio, item, and optional patron.
*
* @async
* @function insertSampleCheckout
* @param {Object} params - Configuration parameters
* @param {Object} [params.patron] - Existing patron to check out to (creates new if not provided)
* @param {string} params.baseUrl - Base URL for API calls
* @param {string} params.authHeader - Authorization header for API calls
* @returns {Promise<Object>} Created checkout with all associated records
* @returns {Object} returns.biblio - The bibliographic record
* @returns {Array<Object>} returns.items - Array of item records
* @returns {Array<Object>} returns.libraries - Array of library records
* @returns {Object} returns.item_type - The item type record
* @returns {Object} returns.checkout - The checkout record
* @returns {Object} [returns.patron] - The patron record (if generated)
* @returns {Object} [returns.patron_category] - The patron category (if generated)
* @example
* // Create a checkout with a new patron
* const checkoutResult = await insertSampleCheckout({
* baseUrl: 'http://localhost:8081',
* authHeader: 'Basic dGVzdDp0ZXN0'
* });
*
* @example
* // Create a checkout for an existing patron
* const checkoutResult = await insertSampleCheckout({
* patron: { patron_id: 123 },
* baseUrl: 'http://localhost:8081',
* authHeader: 'Basic dGVzdDp0ZXN0'
* });
*/
const insertSampleCheckout = async ({ patron, baseUrl, authHeader }) => {
const { biblio, items, libraries, item_type } = await insertSampleBiblio({
item_count: 1,
@ -248,6 +356,35 @@ const insertSampleCheckout = async ({ patron, baseUrl, authHeader }) => {
};
};
/**
* Creates a patron record with associated library and category.
*
* @async
* @function insertSamplePatron
* @param {Object} params - Configuration parameters
* @param {Object} [params.library] - Library to assign patron to (creates new if not provided)
* @param {Object} [params.patron_category] - Patron category to assign (creates new if not provided)
* @param {string} params.baseUrl - Base URL for API calls
* @param {string} params.authHeader - Authorization header for API calls
* @returns {Promise<Object>} Created patron with associated records
* @returns {Object} returns.patron - The created patron record
* @returns {Object} [returns.library] - The library record (if generated)
* @returns {Object} [returns.patron_category] - The patron category record (if generated)
* @example
* // Create a patron with new library and category
* const patronResult = await insertSamplePatron({
* baseUrl: 'http://localhost:8081',
* authHeader: 'Basic dGVzdDp0ZXN0'
* });
*
* @example
* // Create a patron for an existing library
* const patronResult = await insertSamplePatron({
* library: { library_id: 'CPL' },
* baseUrl: 'http://localhost:8081',
* authHeader: 'Basic dGVzdDp0ZXN0'
* });
*/
const insertSamplePatron = async ({
library,
patron_category,
@ -324,6 +461,33 @@ const insertSamplePatron = async ({
};
};
/**
* Deletes test objects from the database in the correct order to respect foreign key constraints.
*
* @async
* @function deleteSampleObjects
* @param {Object|Array<Object>} allObjects - Object(s) to delete, can be single object or array
* @returns {Promise<boolean>} True if deletion was successful
* @description This function handles cleanup of test data by:
* - Accepting single objects or arrays of objects
* - Grouping objects by type (holds, checkouts, patrons, items, etc.)
* - Deleting in dependency order to avoid foreign key violations
* - Supporting all major Koha object types
* @example
* // Delete a single test result
* await deleteSampleObjects(checkoutResult);
*
* @example
* // Delete multiple test results
* await deleteSampleObjects([biblioResult, holdResult, checkoutResult]);
*
* @example
* // Delete after creating test data
* const biblio = await insertSampleBiblio({ item_count: 2, baseUrl, authHeader });
* const hold = await insertSampleHold({ item: biblio.items[0], baseUrl, authHeader });
* // ... run tests ...
* await deleteSampleObjects([biblio, hold]);
*/
const deleteSampleObjects = async allObjects => {
if (!Array.isArray(allObjects)) {
allObjects = [allObjects];
@ -458,6 +622,20 @@ const deleteSampleObjects = async allObjects => {
return true;
};
/**
* Creates a library record via API, filtering out unsupported fields.
*
* @async
* @function insertLibrary
* @param {Object} params - Configuration parameters
* @param {Object} params.library - Library object to insert
* @param {string} params.baseUrl - Base URL for API calls
* @param {string} params.authHeader - Authorization header for API calls
* @returns {Promise<Object>} Created library record
* @private
* @description This is a helper function that removes fields not supported by the API
* before creating the library record.
*/
const insertLibrary = async ({ library, baseUrl, authHeader }) => {
const {
pickup_items,
@ -476,6 +654,31 @@ const insertLibrary = async ({ library, baseUrl, authHeader }) => {
});
};
/**
* Generic function to insert various types of Koha objects.
*
* @async
* @function insertObject
* @param {Object} params - Configuration parameters
* @param {string} params.type - Type of object to insert ('library', 'item_type', 'hold', 'checkout', 'vendor', 'basket')
* @param {Object} params.object - Object data to insert
* @param {string} params.baseUrl - Base URL for API calls
* @param {string} params.authHeader - Authorization header for API calls
* @returns {Promise<Object|boolean>} Created object or true if successful
* @throws {Error} When object type is not supported
* @private
* @description This is a generic helper function that handles the specifics of creating
* different types of Koha objects. Each object type may require different field filtering,
* API endpoints, or database operations.
*
* Supported object types:
* - library: Creates library via API
* - item_type: Creates item type via database query
* - hold: Creates hold via API
* - checkout: Creates checkout via API with confirmation token support
* - vendor: Creates vendor via API
* - basket: Creates basket via database query
*/
const insertObject = async ({ type, object, baseUrl, authHeader }) => {
if (type == "library") {
const keysToKeep = ["library_id", "name"];

View File

@ -1,10 +1,42 @@
/**
* Mock Data Generation for Cypress Testing
*
* This module provides functions to generate realistic test data for Koha objects
* based on OpenAPI schema definitions. It uses Faker.js to generate random data
* that conforms to the API specifications.
*
* @module mockData
*/
const { faker } = require("@faker-js/faker");
const { readYamlFile } = require("./../plugins/readYamlFile.js");
const { query } = require("./db.js");
const fs = require("fs");
/**
* Cache to store generated ID values to prevent duplicates
* @type {Set<string>}
*/
const generatedDataCache = new Set();
/**
* Generates mock data for a specific data type based on OpenAPI schema properties.
*
* @function generateMockData
* @param {string} type - The data type (string, integer, boolean, array, number, date, date-time)
* @param {Object} properties - OpenAPI schema properties for the field
* @param {Array} [properties.enum] - Enumerated values to choose from
* @param {number} [properties.maxLength] - Maximum length for strings
* @param {number} [properties.minLength] - Minimum length for strings
* @returns {*} Generated mock data appropriate for the type
* @private
* @example
* // Generate a string with max length 50
* const name = generateMockData('string', { maxLength: 50 });
*
* // Generate from enum values
* const status = generateMockData('string', { enum: ['active', 'inactive'] });
*/
const generateMockData = (type, properties) => {
if (properties.hasOwnProperty("enum")) {
let values = properties.enum;
@ -55,6 +87,26 @@ const generateMockData = (type, properties) => {
}
};
/**
* Generates mock data for an entire object based on OpenAPI schema properties.
*
* @function generateDataFromSchema
* @param {Object} properties - OpenAPI schema properties object
* @param {Object} [values={}] - Override values for specific fields
* @returns {Object} Generated mock object with all required fields
* @private
* @description This function:
* - Iterates through all properties in the schema
* - Generates appropriate mock data for each field
* - Handles object relationships (libraries, items, etc.)
* - Ensures unique values for ID fields
* - Applies any override values provided
*
* Special handling for object relationships:
* - home_library/holding_library -> generates library object
* - item_type -> generates item_type object
* - Automatically sets corresponding _id fields
*/
const generateDataFromSchema = (properties, values = {}) => {
const mockData = {};
const ids = {};
@ -149,6 +201,37 @@ const generateDataFromSchema = (properties, values = {}) => {
return mockData;
};
/**
* Builds an array of sample objects based on OpenAPI schema definitions.
*
* @function buildSampleObjects
* @param {Object} params - Configuration parameters
* @param {string} params.object - Object type to generate (must match YAML file name)
* @param {Object} [params.values] - Override values for specific fields
* @param {number} [params.count=1] - Number of objects to generate
* @returns {Array<Object>} Array of generated objects
* @throws {Error} When object type is not supported or generation fails
* @description This function:
* - Reads the OpenAPI schema from api/v1/swagger/definitions/{object}.yaml
* - Generates the specified number of objects
* - Applies any override values to all generated objects
* - Ensures all objects conform to the API schema
*
* @example
* // Generate 3 patron objects
* const patrons = buildSampleObjects({
* object: 'patron',
* count: 3
* });
*
* @example
* // Generate 2 items with specific library
* const items = buildSampleObjects({
* object: 'item',
* values: { library_id: 'CPL' },
* count: 2
* });
*/
const buildSampleObjects = ({ object, values, count = 1 }) => {
const yamlPath = `api/v1/swagger/definitions/${object}.yaml`;
if (!fs.existsSync(yamlPath)) {
@ -168,6 +251,43 @@ const buildSampleObjects = ({ object, values, count = 1 }) => {
return generatedObject;
};
/**
* Builds a single sample object based on OpenAPI schema definitions.
*
* @function buildSampleObject
* @param {Object} params - Configuration parameters
* @param {string} params.object - Object type to generate (must match YAML file name)
* @param {Object} [params.values={}] - Override values for specific fields
* @returns {Object} Generated object conforming to API schema
* @throws {Error} When object type is not supported or generation fails
* @description This is a convenience function that generates a single object
* by calling buildSampleObjects with count=1 and returning the first result.
*
* Supported object types include:
* - patron: Library patron/borrower
* - item: Bibliographic item
* - biblio: Bibliographic record
* - library: Library/branch
* - hold: Hold/reservation request
* - checkout: Circulation checkout
* - vendor: Acquisitions vendor
* - basket: Acquisitions basket
* - And others as defined in api/v1/swagger/definitions/
*
* @example
* // Generate a single patron
* const patron = buildSampleObject({ object: 'patron' });
*
* @example
* // Generate an item with specific values
* const item = buildSampleObject({
* object: 'item',
* values: {
* barcode: '12345678',
* home_library_id: 'CPL'
* }
* });
*/
const buildSampleObject = ({ object, values = {} }) => {
return buildSampleObjects({ object, values })[0];
};

View File

@ -1,7 +1,38 @@
/**
* YAML File Reading Utilities for Cypress Testing
*
* This module provides utilities for reading and parsing YAML files,
* primarily used for loading OpenAPI schema definitions during test
* data generation.
*
* @module readYamlFile
*/
const path = require("path");
const fs = require("fs");
const yaml = require("yaml");
/**
* Reads and parses a YAML file.
*
* @function readYamlFile
* @param {string} filePath - Path to the YAML file (relative or absolute)
* @returns {Object} Parsed YAML content as a JavaScript object
* @throws {Error} When file doesn't exist or YAML parsing fails
* @description This function:
* - Resolves the file path to an absolute path
* - Checks if the file exists before attempting to read
* - Reads the file content as UTF-8 text
* - Parses the YAML content into a JavaScript object
*
* @example
* // Read an OpenAPI schema definition
* const patronSchema = readYamlFile('api/v1/swagger/definitions/patron.yaml');
*
* @example
* // Read a configuration file
* const config = readYamlFile('./config/test-config.yaml');
*/
const readYamlFile = filePath => {
const absolutePath = path.resolve(filePath);
if (!fs.existsSync(absolutePath)) {