View | Details | Raw Unified | Return to bug 40447
Collapse All | Expand All

(-)a/t/cypress/plugins/api-client.js (+121 lines)
Lines 1-7 Link Here
1
/**
2
 * Koha API Client for Cypress Testing
3
 *
4
 * This module provides a wrapper around the Koha API client for use in Cypress tests.
5
 * It handles authentication, request preparation, and provides convenient methods
6
 * for making API calls during test execution.
7
 *
8
 * @module api-client
9
 */
10
1
const { APIClient } = require("./dist/api-client.cjs.js");
11
const { APIClient } = require("./dist/api-client.cjs.js");
2
12
3
const client = APIClient.default.koha;
13
const client = APIClient.default.koha;
4
14
15
/**
16
 * Prepares request parameters for API calls by extracting and organizing headers and URL.
17
 *
18
 * @function prepareRequest
19
 * @param {Object} params - Request parameters
20
 * @param {string} params.baseUrl - Base URL for the API
21
 * @param {string} params.endpoint - API endpoint path
22
 * @param {string} [params.authHeader] - Authorization header value
23
 * @param {Object} [params.headers={}] - Additional headers to include
24
 * @param {...*} params.rest - Other parameters to pass through
25
 * @returns {Object} Prepared request object
26
 * @returns {string} returns.url - Complete URL for the request
27
 * @returns {Object} returns.headers - Combined headers object
28
 * @returns {Object} returns.rest - Pass-through parameters
29
 * @private
30
 */
5
const prepareRequest = params => {
31
const prepareRequest = params => {
6
    const { baseUrl, endpoint, authHeader, headers = {}, ...rest } = params;
32
    const { baseUrl, endpoint, authHeader, headers = {}, ...rest } = params;
7
    const url = baseUrl + endpoint;
33
    const url = baseUrl + endpoint;
Lines 12-17 const prepareRequest = params => { Link Here
12
    return { url, headers: finalHeaders, rest };
38
    return { url, headers: finalHeaders, rest };
13
};
39
};
14
40
41
/**
42
 * Performs a GET request to the Koha API.
43
 *
44
 * @function apiGet
45
 * @param {Object} params - Request parameters
46
 * @param {string} params.baseUrl - Base URL for the API
47
 * @param {string} params.endpoint - API endpoint path
48
 * @param {string} [params.authHeader] - Authorization header value
49
 * @param {Object} [params.headers={}] - Additional headers to include
50
 * @param {...*} params.rest - Additional parameters for the request
51
 * @returns {Promise<*>} API response data
52
 * @example
53
 * // Get a list of patrons
54
 * const patrons = await apiGet({
55
 *   baseUrl: 'http://localhost:8081',
56
 *   endpoint: '/api/v1/patrons',
57
 *   authHeader: 'Basic dGVzdDp0ZXN0'
58
 * });
59
 *
60
 * @example
61
 * // Get a specific patron with query parameters
62
 * const patron = await apiGet({
63
 *   baseUrl: 'http://localhost:8081',
64
 *   endpoint: '/api/v1/patrons?q={"patron_id":123}',
65
 *   authHeader: 'Basic dGVzdDp0ZXN0'
66
 * });
67
 */
15
const apiGet = params => {
68
const apiGet = params => {
16
    const { url, headers, rest } = prepareRequest(params);
69
    const { url, headers, rest } = prepareRequest(params);
17
    return client.get({
70
    return client.get({
Lines 21-26 const apiGet = params => { Link Here
21
    });
74
    });
22
};
75
};
23
76
77
/**
78
 * Performs a POST request to the Koha API.
79
 *
80
 * @function apiPost
81
 * @param {Object} params - Request parameters
82
 * @param {string} params.baseUrl - Base URL for the API
83
 * @param {string} params.endpoint - API endpoint path
84
 * @param {string} [params.authHeader] - Authorization header value
85
 * @param {Object} [params.headers={}] - Additional headers to include
86
 * @param {Object} [params.body] - Request body data
87
 * @param {...*} params.rest - Additional parameters for the request
88
 * @returns {Promise<*>} API response data
89
 * @example
90
 * // Create a new patron
91
 * const newPatron = await apiPost({
92
 *   baseUrl: 'http://localhost:8081',
93
 *   endpoint: '/api/v1/patrons',
94
 *   authHeader: 'Basic dGVzdDp0ZXN0',
95
 *   body: {
96
 *     firstname: 'John',
97
 *     surname: 'Doe',
98
 *     library_id: 'CPL',
99
 *     category_id: 'PT'
100
 *   }
101
 * });
102
 */
24
const apiPost = params => {
103
const apiPost = params => {
25
    const { url, headers, rest } = prepareRequest(params);
104
    const { url, headers, rest } = prepareRequest(params);
26
    return client.post({
105
    return client.post({
Lines 30-35 const apiPost = params => { Link Here
30
    });
109
    });
31
};
110
};
32
111
112
/**
113
 * Performs a PUT request to the Koha API.
114
 *
115
 * @function apiPut
116
 * @param {Object} params - Request parameters
117
 * @param {string} params.baseUrl - Base URL for the API
118
 * @param {string} params.endpoint - API endpoint path
119
 * @param {string} [params.authHeader] - Authorization header value
120
 * @param {Object} [params.headers={}] - Additional headers to include
121
 * @param {Object} [params.body] - Request body data
122
 * @param {...*} params.rest - Additional parameters for the request
123
 * @returns {Promise<*>} API response data
124
 * @example
125
 * // Update a patron
126
 * const updatedPatron = await apiPut({
127
 *   baseUrl: 'http://localhost:8081',
128
 *   endpoint: '/api/v1/patrons/123',
129
 *   authHeader: 'Basic dGVzdDp0ZXN0',
130
 *   body: {
131
 *     email: 'newemail@example.com'
132
 *   }
133
 * });
134
 */
33
const apiPut = params => {
135
const apiPut = params => {
34
    const { url, headers, rest } = prepareRequest(params);
136
    const { url, headers, rest } = prepareRequest(params);
35
    return client.put({
137
    return client.put({
Lines 39-44 const apiPut = params => { Link Here
39
    });
141
    });
40
};
142
};
41
143
144
/**
145
 * Performs a DELETE request to the Koha API.
146
 *
147
 * @function apiDelete
148
 * @param {Object} params - Request parameters
149
 * @param {string} params.baseUrl - Base URL for the API
150
 * @param {string} params.endpoint - API endpoint path
151
 * @param {string} [params.authHeader] - Authorization header value
152
 * @param {Object} [params.headers={}] - Additional headers to include
153
 * @param {...*} params.rest - Additional parameters for the request
154
 * @returns {Promise<*>} API response data
155
 * @example
156
 * // Delete a patron
157
 * await apiDelete({
158
 *   baseUrl: 'http://localhost:8081',
159
 *   endpoint: '/api/v1/patrons/123',
160
 *   authHeader: 'Basic dGVzdDp0ZXN0'
161
 * });
162
 */
42
const apiDelete = params => {
163
const apiDelete = params => {
43
    const { url, headers, rest } = prepareRequest(params);
164
    const { url, headers, rest } = prepareRequest(params);
44
    return client.delete({
165
    return client.delete({
(-)a/t/cypress/plugins/auth.js (+28 lines)
Lines 1-5 Link Here
1
/**
2
 * Authentication utilities for Cypress testing
3
 *
4
 * This module provides authentication helper functions for use in Cypress tests
5
 * when making API calls that require authentication.
6
 *
7
 * @module auth
8
 */
9
1
const { Buffer } = require("buffer");
10
const { Buffer } = require("buffer");
2
11
12
/**
13
 * Generates a Basic Authentication header from username and password.
14
 *
15
 * @function getBasicAuthHeader
16
 * @param {string} username - Username for authentication
17
 * @param {string} password - Password for authentication
18
 * @returns {string} Basic authentication header value in format "Basic <base64>"
19
 * @example
20
 * // Generate auth header for API calls
21
 * const authHeader = getBasicAuthHeader('koha', 'koha');
22
 * // Returns: "Basic a29oYTprb2hh"
23
 *
24
 * // Use with API client
25
 * const response = await apiGet({
26
 *   baseUrl: 'http://localhost:8081',
27
 *   endpoint: '/api/v1/patrons',
28
 *   authHeader: getBasicAuthHeader('koha', 'koha')
29
 * });
30
 */
3
const getBasicAuthHeader = (username, password) => {
31
const getBasicAuthHeader = (username, password) => {
4
    const credentials = Buffer.from(`${username}:${password}`).toString(
32
    const credentials = Buffer.from(`${username}:${password}`).toString(
5
        "base64"
33
        "base64"
(-)a/t/cypress/plugins/db.js (+49 lines)
Lines 1-5 Link Here
1
/**
2
 * Database Query Utilities for Cypress Testing
3
 *
4
 * This module provides direct database access for Cypress tests when API
5
 * endpoints are not available or when direct database operations are needed
6
 * for test setup and cleanup.
7
 *
8
 * @module db
9
 */
10
1
const mysql = require("mysql2/promise");
11
const mysql = require("mysql2/promise");
2
12
13
/**
14
 * Database connection configuration
15
 *
16
 * @todo Replace hardcoded credentials with environment variables
17
 * @type {Object}
18
 */
3
const connectionConfig = {
19
const connectionConfig = {
4
    host: "db",
20
    host: "db",
5
    user: "koha_kohadev",
21
    user: "koha_kohadev",
Lines 7-12 const connectionConfig = { Link Here
7
    database: "koha_kohadev",
23
    database: "koha_kohadev",
8
};
24
};
9
25
26
/**
27
 * Executes a SQL query with optional parameters.
28
 *
29
 * @async
30
 * @function query
31
 * @param {string} sql - SQL query string with optional parameter placeholders (?)
32
 * @param {Array} [params=[]] - Array of parameter values for the query
33
 * @returns {Promise<Array>} Query results as an array of rows
34
 * @throws {Error} When database connection or query execution fails
35
 * @description This function:
36
 * - Creates a new database connection for each query
37
 * - Uses parameterized queries to prevent SQL injection
38
 * - Automatically closes the connection after execution
39
 * - Returns the raw result rows from the database
40
 *
41
 * @example
42
 * // Simple SELECT query
43
 * const patrons = await query('SELECT * FROM borrowers LIMIT 10');
44
 *
45
 * @example
46
 * // Parameterized query for safety
47
 * const patron = await query(
48
 *   'SELECT * FROM borrowers WHERE borrowernumber = ?',
49
 *   [123]
50
 * );
51
 *
52
 * @example
53
 * // DELETE query with multiple parameters
54
 * await query(
55
 *   'DELETE FROM issues WHERE issue_id IN (?, ?, ?)',
56
 *   [1, 2, 3]
57
 * );
58
 */
10
async function query(sql, params = []) {
59
async function query(sql, params = []) {
11
    const connection = await mysql.createConnection(connectionConfig);
60
    const connection = await mysql.createConnection(connectionConfig);
12
    const [rows] = await connection.execute(sql, params);
61
    const [rows] = await connection.execute(sql, params);
(-)a/t/cypress/plugins/index.js (+48 lines)
Lines 1-3 Link Here
1
/**
2
 * Cypress Plugin Configuration
3
 *
4
 * This is the main Cypress plugin configuration file that registers all
5
 * testing utilities as Cypress tasks. It provides a bridge between Cypress
6
 * tests and the various utility modules for data generation, API access,
7
 * and database operations.
8
 *
9
 * @module cypress-plugins
10
 */
11
1
const { startDevServer } = require("@cypress/webpack-dev-server");
12
const { startDevServer } = require("@cypress/webpack-dev-server");
2
13
3
const { buildSampleObject, buildSampleObjects } = require("./mockData.js");
14
const { buildSampleObject, buildSampleObjects } = require("./mockData.js");
Lines 17-22 const { query } = require("./db.js"); Link Here
17
28
18
const { apiGet, apiPost, apiPut, apiDelete } = require("./api-client.js");
29
const { apiGet, apiPost, apiPut, apiDelete } = require("./api-client.js");
19
30
31
/**
32
 * Cypress plugin configuration function.
33
 *
34
 * @function
35
 * @param {Function} on - Cypress plugin registration function
36
 * @param {Object} config - Cypress configuration object
37
 * @param {string} config.baseUrl - Base URL for the application under test
38
 * @param {Object} config.env - Environment variables from cypress.config.js
39
 * @param {string} config.env.apiUsername - Username for API authentication
40
 * @param {string} config.env.apiPassword - Password for API authentication
41
 * @returns {Object} Modified Cypress configuration
42
 * @description This function:
43
 * - Registers all testing utilities as Cypress tasks
44
 * - Sets up authentication headers for API calls
45
 * - Configures the development server for component testing
46
 * - Provides automatic parameter injection for common arguments
47
 *
48
 * Available Cypress tasks:
49
 * - Data Generation: buildSampleObject, buildSampleObjects
50
 * - Data Insertion: insertSampleBiblio, insertSampleHold, insertSampleCheckout, insertSamplePatron
51
 * - Data Cleanup: deleteSampleObjects
52
 * - API Access: apiGet, apiPost, apiPut, apiDelete
53
 * - Database Access: query
54
 * - Authentication: getBasicAuthHeader
55
 *
56
 * @example
57
 * // Usage in Cypress tests
58
 * cy.task('insertSampleBiblio', { item_count: 2 }).then(result => {
59
 *   // Test with the created biblio
60
 * });
61
 *
62
 * @example
63
 * // API call through task
64
 * cy.task('apiGet', { endpoint: '/api/v1/patrons' }).then(patrons => {
65
 *   // Work with patron data
66
 * });
67
 */
20
module.exports = (on, config) => {
68
module.exports = (on, config) => {
21
    const baseUrl = config.baseUrl;
69
    const baseUrl = config.baseUrl;
22
    const authHeader = getBasicAuthHeader(
70
    const authHeader = getBasicAuthHeader(
(-)a/t/cypress/plugins/insertData.js (+203 lines)
Lines 1-8 Link Here
1
/**
2
 * Koha Cypress Testing Data Insertion Utilities
3
 *
4
 * This module provides functions to create and manage test data for Cypress tests.
5
 * It handles creating complete bibliographic records, patrons, holds, checkouts,
6
 * and other Koha objects with proper relationships and dependencies.
7
 *
8
 * @module insertData
9
 */
10
1
const { buildSampleObject, buildSampleObjects } = require("./mockData.js");
11
const { buildSampleObject, buildSampleObjects } = require("./mockData.js");
2
const { query } = require("./db.js");
12
const { query } = require("./db.js");
3
13
4
const { apiGet, apiPost } = require("./api-client.js");
14
const { apiGet, apiPost } = require("./api-client.js");
5
15
16
/**
17
 * Creates a complete bibliographic record with associated items and libraries.
18
 *
19
 * @async
20
 * @function insertSampleBiblio
21
 * @param {Object} params - Configuration parameters
22
 * @param {number} params.item_count - Number of items to create for this biblio
23
 * @param {Object} [params.options] - Additional options
24
 * @param {boolean} [params.options.different_libraries] - If true, creates different libraries for each item
25
 * @param {string} params.baseUrl - Base URL for API calls
26
 * @param {string} params.authHeader - Authorization header for API calls
27
 * @returns {Promise<Object>} Created biblio with items, libraries, and item_type
28
 * @returns {Object} returns.biblio - The created bibliographic record
29
 * @returns {Array<Object>} returns.items - Array of created item records
30
 * @returns {Array<Object>} returns.libraries - Array of created library records
31
 * @returns {Object} returns.item_type - The created item type record
32
 * @example
33
 * // Create a biblio with 3 items using the same library
34
 * const result = await insertSampleBiblio({
35
 *   item_count: 3,
36
 *   baseUrl: 'http://localhost:8081',
37
 *   authHeader: 'Basic dGVzdDp0ZXN0'
38
 * });
39
 *
40
 * @example
41
 * // Create a biblio with 2 items using different libraries
42
 * const result = await insertSampleBiblio({
43
 *   item_count: 2,
44
 *   options: { different_libraries: true },
45
 *   baseUrl: 'http://localhost:8081',
46
 *   authHeader: 'Basic dGVzdDp0ZXN0'
47
 * });
48
 */
6
const insertSampleBiblio = async ({
49
const insertSampleBiblio = async ({
7
    item_count,
50
    item_count,
8
    options,
51
    options,
Lines 160-165 const insertSampleBiblio = async ({ Link Here
160
    return { biblio, items: createdItems, libraries, item_type };
203
    return { biblio, items: createdItems, libraries, item_type };
161
};
204
};
162
205
206
/**
207
 * Creates a hold request for a bibliographic record or item.
208
 *
209
 * @async
210
 * @function insertSampleHold
211
 * @param {Object} params - Configuration parameters
212
 * @param {Object} [params.item] - Item to place hold on (optional if biblio provided)
213
 * @param {Object} [params.biblio] - Biblio to place hold on (optional if item provided)
214
 * @param {string} [params.library_id] - Library ID for pickup location (defaults to item's home library)
215
 * @param {string} params.baseUrl - Base URL for API calls
216
 * @param {string} params.authHeader - Authorization header for API calls
217
 * @returns {Promise<Object>} Created hold with associated patron and patron_category
218
 * @returns {Object} returns.hold - The created hold record
219
 * @returns {Object} returns.patron - The patron who placed the hold
220
 * @returns {Object} returns.patron_category - The patron's category
221
 * @throws {Error} When neither library_id nor item is provided
222
 * @example
223
 * // Create a hold on a specific item
224
 * const holdResult = await insertSampleHold({
225
 *   item: { item_id: 123, home_library_id: 'CPL' },
226
 *   baseUrl: 'http://localhost:8081',
227
 *   authHeader: 'Basic dGVzdDp0ZXN0'
228
 * });
229
 *
230
 * @example
231
 * // Create a biblio-level hold
232
 * const holdResult = await insertSampleHold({
233
 *   biblio: { biblio_id: 456 },
234
 *   library_id: 'CPL',
235
 *   baseUrl: 'http://localhost:8081',
236
 *   authHeader: 'Basic dGVzdDp0ZXN0'
237
 * });
238
 */
163
const insertSampleHold = async ({
239
const insertSampleHold = async ({
164
    item,
240
    item,
165
    biblio,
241
    biblio,
Lines 199-204 const insertSampleHold = async ({ Link Here
199
    return { hold, patron, patron_category };
275
    return { hold, patron, patron_category };
200
};
276
};
201
277
278
/**
279
 * Creates a checkout record with associated biblio, item, and optional patron.
280
 *
281
 * @async
282
 * @function insertSampleCheckout
283
 * @param {Object} params - Configuration parameters
284
 * @param {Object} [params.patron] - Existing patron to check out to (creates new if not provided)
285
 * @param {string} params.baseUrl - Base URL for API calls
286
 * @param {string} params.authHeader - Authorization header for API calls
287
 * @returns {Promise<Object>} Created checkout with all associated records
288
 * @returns {Object} returns.biblio - The bibliographic record
289
 * @returns {Array<Object>} returns.items - Array of item records
290
 * @returns {Array<Object>} returns.libraries - Array of library records
291
 * @returns {Object} returns.item_type - The item type record
292
 * @returns {Object} returns.checkout - The checkout record
293
 * @returns {Object} [returns.patron] - The patron record (if generated)
294
 * @returns {Object} [returns.patron_category] - The patron category (if generated)
295
 * @example
296
 * // Create a checkout with a new patron
297
 * const checkoutResult = await insertSampleCheckout({
298
 *   baseUrl: 'http://localhost:8081',
299
 *   authHeader: 'Basic dGVzdDp0ZXN0'
300
 * });
301
 *
302
 * @example
303
 * // Create a checkout for an existing patron
304
 * const checkoutResult = await insertSampleCheckout({
305
 *   patron: { patron_id: 123 },
306
 *   baseUrl: 'http://localhost:8081',
307
 *   authHeader: 'Basic dGVzdDp0ZXN0'
308
 * });
309
 */
202
const insertSampleCheckout = async ({ patron, baseUrl, authHeader }) => {
310
const insertSampleCheckout = async ({ patron, baseUrl, authHeader }) => {
203
    const { biblio, items, libraries, item_type } = await insertSampleBiblio({
311
    const { biblio, items, libraries, item_type } = await insertSampleBiblio({
204
        item_count: 1,
312
        item_count: 1,
Lines 248-253 const insertSampleCheckout = async ({ patron, baseUrl, authHeader }) => { Link Here
248
    };
356
    };
249
};
357
};
250
358
359
/**
360
 * Creates a patron record with associated library and category.
361
 *
362
 * @async
363
 * @function insertSamplePatron
364
 * @param {Object} params - Configuration parameters
365
 * @param {Object} [params.library] - Library to assign patron to (creates new if not provided)
366
 * @param {Object} [params.patron_category] - Patron category to assign (creates new if not provided)
367
 * @param {string} params.baseUrl - Base URL for API calls
368
 * @param {string} params.authHeader - Authorization header for API calls
369
 * @returns {Promise<Object>} Created patron with associated records
370
 * @returns {Object} returns.patron - The created patron record
371
 * @returns {Object} [returns.library] - The library record (if generated)
372
 * @returns {Object} [returns.patron_category] - The patron category record (if generated)
373
 * @example
374
 * // Create a patron with new library and category
375
 * const patronResult = await insertSamplePatron({
376
 *   baseUrl: 'http://localhost:8081',
377
 *   authHeader: 'Basic dGVzdDp0ZXN0'
378
 * });
379
 *
380
 * @example
381
 * // Create a patron for an existing library
382
 * const patronResult = await insertSamplePatron({
383
 *   library: { library_id: 'CPL' },
384
 *   baseUrl: 'http://localhost:8081',
385
 *   authHeader: 'Basic dGVzdDp0ZXN0'
386
 * });
387
 */
251
const insertSamplePatron = async ({
388
const insertSamplePatron = async ({
252
    library,
389
    library,
253
    patron_category,
390
    patron_category,
Lines 324-329 const insertSamplePatron = async ({ Link Here
324
    };
461
    };
325
};
462
};
326
463
464
/**
465
 * Deletes test objects from the database in the correct order to respect foreign key constraints.
466
 *
467
 * @async
468
 * @function deleteSampleObjects
469
 * @param {Object|Array<Object>} allObjects - Object(s) to delete, can be single object or array
470
 * @returns {Promise<boolean>} True if deletion was successful
471
 * @description This function handles cleanup of test data by:
472
 * - Accepting single objects or arrays of objects
473
 * - Grouping objects by type (holds, checkouts, patrons, items, etc.)
474
 * - Deleting in dependency order to avoid foreign key violations
475
 * - Supporting all major Koha object types
476
 * @example
477
 * // Delete a single test result
478
 * await deleteSampleObjects(checkoutResult);
479
 *
480
 * @example
481
 * // Delete multiple test results
482
 * await deleteSampleObjects([biblioResult, holdResult, checkoutResult]);
483
 *
484
 * @example
485
 * // Delete after creating test data
486
 * const biblio = await insertSampleBiblio({ item_count: 2, baseUrl, authHeader });
487
 * const hold = await insertSampleHold({ item: biblio.items[0], baseUrl, authHeader });
488
 * // ... run tests ...
489
 * await deleteSampleObjects([biblio, hold]);
490
 */
327
const deleteSampleObjects = async allObjects => {
491
const deleteSampleObjects = async allObjects => {
328
    if (!Array.isArray(allObjects)) {
492
    if (!Array.isArray(allObjects)) {
329
        allObjects = [allObjects];
493
        allObjects = [allObjects];
Lines 458-463 const deleteSampleObjects = async allObjects => { Link Here
458
    return true;
622
    return true;
459
};
623
};
460
624
625
/**
626
 * Creates a library record via API, filtering out unsupported fields.
627
 *
628
 * @async
629
 * @function insertLibrary
630
 * @param {Object} params - Configuration parameters
631
 * @param {Object} params.library - Library object to insert
632
 * @param {string} params.baseUrl - Base URL for API calls
633
 * @param {string} params.authHeader - Authorization header for API calls
634
 * @returns {Promise<Object>} Created library record
635
 * @private
636
 * @description This is a helper function that removes fields not supported by the API
637
 * before creating the library record.
638
 */
461
const insertLibrary = async ({ library, baseUrl, authHeader }) => {
639
const insertLibrary = async ({ library, baseUrl, authHeader }) => {
462
    const {
640
    const {
463
        pickup_items,
641
        pickup_items,
Lines 476-481 const insertLibrary = async ({ library, baseUrl, authHeader }) => { Link Here
476
    });
654
    });
477
};
655
};
478
656
657
/**
658
 * Generic function to insert various types of Koha objects.
659
 *
660
 * @async
661
 * @function insertObject
662
 * @param {Object} params - Configuration parameters
663
 * @param {string} params.type - Type of object to insert ('library', 'item_type', 'hold', 'checkout', 'vendor', 'basket')
664
 * @param {Object} params.object - Object data to insert
665
 * @param {string} params.baseUrl - Base URL for API calls
666
 * @param {string} params.authHeader - Authorization header for API calls
667
 * @returns {Promise<Object|boolean>} Created object or true if successful
668
 * @throws {Error} When object type is not supported
669
 * @private
670
 * @description This is a generic helper function that handles the specifics of creating
671
 * different types of Koha objects. Each object type may require different field filtering,
672
 * API endpoints, or database operations.
673
 *
674
 * Supported object types:
675
 * - library: Creates library via API
676
 * - item_type: Creates item type via database query
677
 * - hold: Creates hold via API
678
 * - checkout: Creates checkout via API with confirmation token support
679
 * - vendor: Creates vendor via API
680
 * - basket: Creates basket via database query
681
 */
479
const insertObject = async ({ type, object, baseUrl, authHeader }) => {
682
const insertObject = async ({ type, object, baseUrl, authHeader }) => {
480
    if (type == "library") {
683
    if (type == "library") {
481
        const keysToKeep = ["library_id", "name"];
684
        const keysToKeep = ["library_id", "name"];
(-)a/t/cypress/plugins/mockData.js (+120 lines)
Lines 1-10 Link Here
1
/**
2
 * Mock Data Generation for Cypress Testing
3
 *
4
 * This module provides functions to generate realistic test data for Koha objects
5
 * based on OpenAPI schema definitions. It uses Faker.js to generate random data
6
 * that conforms to the API specifications.
7
 *
8
 * @module mockData
9
 */
10
1
const { faker } = require("@faker-js/faker");
11
const { faker } = require("@faker-js/faker");
2
const { readYamlFile } = require("./../plugins/readYamlFile.js");
12
const { readYamlFile } = require("./../plugins/readYamlFile.js");
3
const { query } = require("./db.js");
13
const { query } = require("./db.js");
4
const fs = require("fs");
14
const fs = require("fs");
5
15
16
/**
17
 * Cache to store generated ID values to prevent duplicates
18
 * @type {Set<string>}
19
 */
6
const generatedDataCache = new Set();
20
const generatedDataCache = new Set();
7
21
22
/**
23
 * Generates mock data for a specific data type based on OpenAPI schema properties.
24
 *
25
 * @function generateMockData
26
 * @param {string} type - The data type (string, integer, boolean, array, number, date, date-time)
27
 * @param {Object} properties - OpenAPI schema properties for the field
28
 * @param {Array} [properties.enum] - Enumerated values to choose from
29
 * @param {number} [properties.maxLength] - Maximum length for strings
30
 * @param {number} [properties.minLength] - Minimum length for strings
31
 * @returns {*} Generated mock data appropriate for the type
32
 * @private
33
 * @example
34
 * // Generate a string with max length 50
35
 * const name = generateMockData('string', { maxLength: 50 });
36
 *
37
 * // Generate from enum values
38
 * const status = generateMockData('string', { enum: ['active', 'inactive'] });
39
 */
8
const generateMockData = (type, properties) => {
40
const generateMockData = (type, properties) => {
9
    if (properties.hasOwnProperty("enum")) {
41
    if (properties.hasOwnProperty("enum")) {
10
        let values = properties.enum;
42
        let values = properties.enum;
Lines 55-60 const generateMockData = (type, properties) => { Link Here
55
    }
87
    }
56
};
88
};
57
89
90
/**
91
 * Generates mock data for an entire object based on OpenAPI schema properties.
92
 *
93
 * @function generateDataFromSchema
94
 * @param {Object} properties - OpenAPI schema properties object
95
 * @param {Object} [values={}] - Override values for specific fields
96
 * @returns {Object} Generated mock object with all required fields
97
 * @private
98
 * @description This function:
99
 * - Iterates through all properties in the schema
100
 * - Generates appropriate mock data for each field
101
 * - Handles object relationships (libraries, items, etc.)
102
 * - Ensures unique values for ID fields
103
 * - Applies any override values provided
104
 *
105
 * Special handling for object relationships:
106
 * - home_library/holding_library -> generates library object
107
 * - item_type -> generates item_type object
108
 * - Automatically sets corresponding _id fields
109
 */
58
const generateDataFromSchema = (properties, values = {}) => {
110
const generateDataFromSchema = (properties, values = {}) => {
59
    const mockData = {};
111
    const mockData = {};
60
    const ids = {};
112
    const ids = {};
Lines 149-154 const generateDataFromSchema = (properties, values = {}) => { Link Here
149
    return mockData;
201
    return mockData;
150
};
202
};
151
203
204
/**
205
 * Builds an array of sample objects based on OpenAPI schema definitions.
206
 *
207
 * @function buildSampleObjects
208
 * @param {Object} params - Configuration parameters
209
 * @param {string} params.object - Object type to generate (must match YAML file name)
210
 * @param {Object} [params.values] - Override values for specific fields
211
 * @param {number} [params.count=1] - Number of objects to generate
212
 * @returns {Array<Object>} Array of generated objects
213
 * @throws {Error} When object type is not supported or generation fails
214
 * @description This function:
215
 * - Reads the OpenAPI schema from api/v1/swagger/definitions/{object}.yaml
216
 * - Generates the specified number of objects
217
 * - Applies any override values to all generated objects
218
 * - Ensures all objects conform to the API schema
219
 *
220
 * @example
221
 * // Generate 3 patron objects
222
 * const patrons = buildSampleObjects({
223
 *   object: 'patron',
224
 *   count: 3
225
 * });
226
 *
227
 * @example
228
 * // Generate 2 items with specific library
229
 * const items = buildSampleObjects({
230
 *   object: 'item',
231
 *   values: { library_id: 'CPL' },
232
 *   count: 2
233
 * });
234
 */
152
const buildSampleObjects = ({ object, values, count = 1 }) => {
235
const buildSampleObjects = ({ object, values, count = 1 }) => {
153
    const yamlPath = `api/v1/swagger/definitions/${object}.yaml`;
236
    const yamlPath = `api/v1/swagger/definitions/${object}.yaml`;
154
    if (!fs.existsSync(yamlPath)) {
237
    if (!fs.existsSync(yamlPath)) {
Lines 168-173 const buildSampleObjects = ({ object, values, count = 1 }) => { Link Here
168
    return generatedObject;
251
    return generatedObject;
169
};
252
};
170
253
254
/**
255
 * Builds a single sample object based on OpenAPI schema definitions.
256
 *
257
 * @function buildSampleObject
258
 * @param {Object} params - Configuration parameters
259
 * @param {string} params.object - Object type to generate (must match YAML file name)
260
 * @param {Object} [params.values={}] - Override values for specific fields
261
 * @returns {Object} Generated object conforming to API schema
262
 * @throws {Error} When object type is not supported or generation fails
263
 * @description This is a convenience function that generates a single object
264
 * by calling buildSampleObjects with count=1 and returning the first result.
265
 *
266
 * Supported object types include:
267
 * - patron: Library patron/borrower
268
 * - item: Bibliographic item
269
 * - biblio: Bibliographic record
270
 * - library: Library/branch
271
 * - hold: Hold/reservation request
272
 * - checkout: Circulation checkout
273
 * - vendor: Acquisitions vendor
274
 * - basket: Acquisitions basket
275
 * - And others as defined in api/v1/swagger/definitions/
276
 *
277
 * @example
278
 * // Generate a single patron
279
 * const patron = buildSampleObject({ object: 'patron' });
280
 *
281
 * @example
282
 * // Generate an item with specific values
283
 * const item = buildSampleObject({
284
 *   object: 'item',
285
 *   values: {
286
 *     barcode: '12345678',
287
 *     home_library_id: 'CPL'
288
 *   }
289
 * });
290
 */
171
const buildSampleObject = ({ object, values = {} }) => {
291
const buildSampleObject = ({ object, values = {} }) => {
172
    return buildSampleObjects({ object, values })[0];
292
    return buildSampleObjects({ object, values })[0];
173
};
293
};
(-)a/t/cypress/plugins/readYamlFile.js (-1 / +31 lines)
Lines 1-7 Link Here
1
/**
2
 * YAML File Reading Utilities for Cypress Testing
3
 *
4
 * This module provides utilities for reading and parsing YAML files,
5
 * primarily used for loading OpenAPI schema definitions during test
6
 * data generation.
7
 *
8
 * @module readYamlFile
9
 */
10
1
const path = require("path");
11
const path = require("path");
2
const fs = require("fs");
12
const fs = require("fs");
3
const yaml = require("yaml");
13
const yaml = require("yaml");
4
14
15
/**
16
 * Reads and parses a YAML file.
17
 *
18
 * @function readYamlFile
19
 * @param {string} filePath - Path to the YAML file (relative or absolute)
20
 * @returns {Object} Parsed YAML content as a JavaScript object
21
 * @throws {Error} When file doesn't exist or YAML parsing fails
22
 * @description This function:
23
 * - Resolves the file path to an absolute path
24
 * - Checks if the file exists before attempting to read
25
 * - Reads the file content as UTF-8 text
26
 * - Parses the YAML content into a JavaScript object
27
 *
28
 * @example
29
 * // Read an OpenAPI schema definition
30
 * const patronSchema = readYamlFile('api/v1/swagger/definitions/patron.yaml');
31
 *
32
 * @example
33
 * // Read a configuration file
34
 * const config = readYamlFile('./config/test-config.yaml');
35
 */
5
const readYamlFile = filePath => {
36
const readYamlFile = filePath => {
6
    const absolutePath = path.resolve(filePath);
37
    const absolutePath = path.resolve(filePath);
7
    if (!fs.existsSync(absolutePath)) {
38
    if (!fs.existsSync(absolutePath)) {
8
- 

Return to bug 40447