pgsql-test
Version:
pgsql-test offers isolated, role-aware, and rollback-friendly PostgreSQL environments for integration tests — giving developers realistic test coverage without external state pollution
129 lines (128 loc) • 5.3 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.snapshot = exports.createSnapshot = exports.prune = exports.defaultPruners = exports.composePruners = exports.pruneTokens = exports.prunePeoplestamps = exports.pruneSchemas = exports.pruneHashes = exports.pruneUUIDs = exports.pruneIdArrays = exports.pruneIds = exports.pruneDates = exports.formatPgError = exports.formatPgErrorFields = exports.extractPgErrorFields = void 0;
exports.getErrorCode = getErrorCode;
const types_1 = require("@pgpmjs/types");
Object.defineProperty(exports, "extractPgErrorFields", { enumerable: true, get: function () { return types_1.extractPgErrorFields; } });
Object.defineProperty(exports, "formatPgErrorFields", { enumerable: true, get: function () { return types_1.formatPgErrorFields; } });
Object.defineProperty(exports, "formatPgError", { enumerable: true, get: function () { return types_1.formatPgError; } });
/**
* Extract the error code from an error message.
*
* Enhanced error messages from PgTestClient include additional context on subsequent lines
* (Where, Query, Values, etc.). This function returns only the first line, which contains
* the actual error code raised by PostgreSQL.
*
* @param message - The error message (may contain multiple lines with debug context)
* @returns The first line of the error message (the error code)
*
* @example
* // Error message with enhanced context:
* // "NONEXISTENT_TYPE\nWhere: PL/pgSQL function...\nQuery: INSERT INTO..."
* getErrorCode(err.message) // => "NONEXISTENT_TYPE"
*/
function getErrorCode(message) {
return message.split('\n')[0];
}
const uuidRegexp = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
const idReplacement = (v, idHash) => {
if (!v)
return v;
if (!idHash)
return '[ID]';
const key = String(v);
return idHash[key] !== undefined ? `[ID-${idHash[key]}]` : '[ID]';
};
function mapValues(obj, fn) {
return Object.entries(obj).reduce((acc, [key, value]) => {
acc[key] = fn(value, key);
return acc;
}, {});
}
const pruneDates = (row) => mapValues(row, (v, k) => {
if (!v) {
return v;
}
if (v instanceof Date) {
return '[DATE]';
}
else if (typeof v === 'string' &&
/(_at|At)$/.test(k) &&
/^20[0-9]{2}-[0-9]{2}-[0-9]{2}/.test(v)) {
return '[DATE]';
}
return v;
});
exports.pruneDates = pruneDates;
const pruneIds = (row, idHash) => mapValues(row, (v, k) => (k === 'id' || (typeof k === 'string' && k.endsWith('_id'))) &&
(typeof v === 'string' || typeof v === 'number')
? idReplacement(v, idHash)
: v);
exports.pruneIds = pruneIds;
const pruneIdArrays = (row) => mapValues(row, (v, k) => typeof k === 'string' && k.endsWith('_ids') && Array.isArray(v)
? `[UUIDs-${v.length}]`
: v);
exports.pruneIdArrays = pruneIdArrays;
const pruneUUIDs = (row) => mapValues(row, (v, k) => {
if (typeof v !== 'string') {
return v;
}
if (['uuid', 'queue_name'].includes(k) && uuidRegexp.test(v)) {
return '[UUID]';
}
if (k === 'gravatar' && /^[0-9a-f]{32}$/i.test(v)) {
return '[gUUID]';
}
return v;
});
exports.pruneUUIDs = pruneUUIDs;
const pruneHashes = (row) => mapValues(row, (v, k) => typeof k === 'string' &&
k.endsWith('_hash') &&
typeof v === 'string' &&
v.startsWith('$')
? '[hash]'
: v);
exports.pruneHashes = pruneHashes;
const pruneSchemas = (row) => mapValues(row, (v, k) => typeof v === 'string' && /^zz-/.test(v) ? '[schemahash]' : v);
exports.pruneSchemas = pruneSchemas;
const prunePeoplestamps = (row) => mapValues(row, (v, k) => k.endsWith('_by') && typeof v === 'string' ? '[peoplestamp]' : v);
exports.prunePeoplestamps = prunePeoplestamps;
const pruneTokens = (row) => mapValues(row, (v, k) => (k === 'token' || k.endsWith('_token')) && typeof v === 'string'
? '[token]'
: v);
exports.pruneTokens = pruneTokens;
const composePruners = (...pruners) => (row) => pruners.reduce((acc, pruner) => pruner(acc), row);
exports.composePruners = composePruners;
// Default pruners used by prune/snapshot (without IdHash)
exports.defaultPruners = [
exports.pruneTokens,
exports.prunePeoplestamps,
exports.pruneDates,
exports.pruneIdArrays,
exports.pruneUUIDs,
exports.pruneHashes
];
// Compose pruners and apply pruneIds with IdHash support
const prune = (row, idHash) => {
const pruned = (0, exports.composePruners)(...exports.defaultPruners)(row);
return (0, exports.pruneIds)(pruned, idHash);
};
exports.prune = prune;
// Factory to create a snapshot function with custom pruners
const createSnapshot = (pruners) => {
const pruneFn = (0, exports.composePruners)(...pruners);
const snap = (obj, idHash) => {
if (Array.isArray(obj)) {
return obj.map((el) => snap(el, idHash));
}
else if (obj && typeof obj === 'object') {
const pruned = pruneFn(obj);
const prunedWithIds = (0, exports.pruneIds)(pruned, idHash);
return mapValues(prunedWithIds, (v) => snap(v, idHash));
}
return obj;
};
return snap;
};
exports.createSnapshot = createSnapshot;
exports.snapshot = (0, exports.createSnapshot)(exports.defaultPruners);