Release v2.19.1: repair managed font references
This commit is contained in:
1 parent
90253e98a4
commit
9ca372429b
11 files changed
+212
-23
No files matched your search
@@ -38,6 +38,20 @@ test('content reference extraction deduplicates repeated references within a reg
|
||||
]);
|
||||
});
|
||||
|
||||
test('content reference extraction decodes HTML-encoded font names before parsing CSS', () => {
|
||||
const references = collectContentReferences('slide', 12, {
|
||||
numeric: { value: '<span style="font-family: 'Ubuntu';">Ubuntu</span>', font_family: 'Arial' },
|
||||
named: { value: '<span style="font-family: "Ubuntu Sans", sans-serif;">Ubuntu</span>' },
|
||||
hex: { value: '<span style="font-family: 'Ubuntu';">Ubuntu</span>', font_family: 'Ubuntu' }
|
||||
});
|
||||
|
||||
assert.deepEqual(references.map(reference => [reference.owner_key, reference.reference_value]), [
|
||||
['numeric', 'ubuntu'],
|
||||
['named', 'ubuntu sans'],
|
||||
['hex', 'ubuntu']
|
||||
]);
|
||||
});
|
||||
|
||||
test('content reference replacement preserves unchanged rows and only deletes or inserts the delta', async () => {
|
||||
const calls = [];
|
||||
const connection = {
|
||||
@@ -66,4 +80,44 @@ test('content reference replacement preserves unchanged rows and only deletes or
|
||||
assert.equal(insertion[1][0].length, 2);
|
||||
assert.deepEqual(insertion[1][0].map(row => [row[3], row[4]]), [['api-request', '5'], ['rss-feed', '8']]);
|
||||
assert.ok(!calls.some(([sql]) => sql === 'DELETE FROM c_content_references WHERE owner_type = ? AND owner_id = ?'));
|
||||
});
|
||||
|
||||
test('content reference replacement repairs encoded font references and removes the base font', async () => {
|
||||
const calls = [];
|
||||
const connection = {
|
||||
async query(sql, params) {
|
||||
calls.push([sql, params]);
|
||||
if (sql.startsWith('SELECT id, owner_key')) {
|
||||
return [[
|
||||
{ id: 101, owner_key: 'second_place', reference_type: 'font-family', reference_value: ''' },
|
||||
{ id: 102, owner_key: 'second_place', reference_type: 'font-family', reference_value: 'arial' }
|
||||
]];
|
||||
}
|
||||
return [{}];
|
||||
}
|
||||
};
|
||||
|
||||
await replaceContentReferences(connection, 'slide', 22, {
|
||||
second_place: { value: '<span style="font-family: 'Ubuntu';">Second</span>', font_family: 'Arial' }
|
||||
});
|
||||
|
||||
const deletion = calls.find(([sql]) => sql.startsWith('DELETE FROM c_content_references'));
|
||||
assert.deepEqual(deletion[1], ['slide', 22, [101, 102]]);
|
||||
const insertion = calls.find(([sql]) => sql.startsWith('INSERT IGNORE INTO c_content_references'));
|
||||
assert.deepEqual(insertion[1][0].map(row => [row[2], row[3], row[4]]), [
|
||||
['second_place', 'font-family', 'ubuntu']
|
||||
]);
|
||||
});
|
||||
|
||||
test('content references omit built-in font stacks but retain managed font families', () => {
|
||||
const references = collectContentReferences('slide', 117, {
|
||||
base: { font_family: 'Arial, Helvetica, sans-serif' },
|
||||
quoted: { fontFamily: "'TREBUCHET MS', Geneva, sans-serif" },
|
||||
generic: { value: '<p style="font-family: system-ui, serif, monospace, cursive, fantasy;">Base</p>' },
|
||||
managed: { font_family: 'Arial', value: '<span style="font-family: 'Ubuntu Regular', Helvetica, sans-serif;">Second</span>' }
|
||||
});
|
||||
|
||||
assert.deepEqual(references.map(reference => [reference.owner_key, reference.reference_value]), [
|
||||
['managed', 'ubuntu regular']
|
||||
]);
|
||||
});
|
||||
@@ -9,6 +9,11 @@ function createPool(responses) {
|
||||
const queries = [];
|
||||
return {
|
||||
queries,
|
||||
async getConnection() { return this; },
|
||||
async beginTransaction() { queries.push(['START TRANSACTION']); },
|
||||
async commit() { queries.push(['COMMIT']); },
|
||||
async rollback() { queries.push(['ROLLBACK']); },
|
||||
release() { queries.push(['RELEASE CONNECTION']); },
|
||||
async query(sql, params) {
|
||||
queries.push([sql, params]);
|
||||
const text = String(sql);
|
||||
@@ -200,4 +205,62 @@ test('2.17.1 migration removes obsolete dashboard.allow grants and permission',
|
||||
assert.ok(pool.queries.some(function ([sql, params]) {
|
||||
return String(sql).includes('DELETE FROM a_permissions') && params[0] === 'dashboard.allow';
|
||||
}));
|
||||
});
|
||||
|
||||
test('2.19.1 migration rebuilds only managed font references for all content in a transaction', async () => {
|
||||
const pool = createPool([
|
||||
{ match: sql => sql.includes('information_schema.COLUMNS'), result: [[{ column_count: 1 }]] },
|
||||
{ match: sql => sql.includes('information_schema.TABLES'), result: [[{ table_count: 0 }]] },
|
||||
{
|
||||
match: (sql, params) => sql.startsWith('SELECT id, content_json') && params[0] === 0,
|
||||
result: [[{ id: 117, content_json: JSON.stringify({ second_place: {
|
||||
font_family: 'Arial', value: '<span style="font-family: 'Ubuntu Regular', Helvetica, sans-serif;">Second</span>',
|
||||
source_id: 4, image: '/media/uploads/keep.png'
|
||||
} }) }]]
|
||||
},
|
||||
{
|
||||
match: (sql, params) => sql.startsWith('SELECT id, content_json') && params[0] === 117,
|
||||
result: [[{ id: 118, content_json: { hero: { font_family: 'Acme Display' } } }]]
|
||||
},
|
||||
{
|
||||
match: (sql, params) => sql.startsWith('SELECT id, template_id, region_key') && params[0] === 0,
|
||||
result: [[{ id: 900, template_id: 42, region_key: 'hero', default_content_json: { font_family: 'Acme Display', feed_id: 8 } }]]
|
||||
}
|
||||
]);
|
||||
|
||||
const pending = await getPendingMigrations(pool, { currentVersion: '2.19.0' });
|
||||
assert.deepEqual(pending.map(migration => migration.version), ['2.19.1']);
|
||||
await pending[0].run(pool);
|
||||
|
||||
const deletions = pool.queries.filter(([sql]) => sql.startsWith('DELETE'));
|
||||
assert.deepEqual(deletions, [['DELETE FROM c_content_references WHERE reference_type = ?', ['font-family']]]);
|
||||
const inserted = pool.queries.filter(([sql]) => sql.startsWith('INSERT IGNORE INTO c_content_references'))
|
||||
.flatMap(([, params]) => params[0]);
|
||||
assert.deepEqual(inserted.map(row => row.slice(0, 5)), [
|
||||
['slide', 117, 'second_place', 'font-family', 'ubuntu regular'],
|
||||
['slide', 118, 'hero', 'font-family', 'acme display'],
|
||||
['template', 42, 'hero', 'font-family', 'acme display']
|
||||
]);
|
||||
assert.ok(pool.queries.some(([sql, params]) => sql.startsWith('SELECT id, content_json') && params[0] === 118));
|
||||
assert.ok(pool.queries.some(([sql, params]) => sql.startsWith('SELECT id, template_id, region_key') && params[0] === 900));
|
||||
assert.ok(!pool.queries.some(([sql]) => /^(UPDATE|ALTER|TRUNCATE)/.test(sql)));
|
||||
assert.deepEqual(pool.queries.slice(-2), [['COMMIT'], ['RELEASE CONNECTION']]);
|
||||
assert.deepEqual(await getPendingMigrations(pool, { currentVersion: '2.19.1' }), []);
|
||||
});
|
||||
|
||||
test('2.19.1 font repair rolls back and releases its connection on failure', async () => {
|
||||
const pool = createPool([
|
||||
{ match: sql => sql.includes('information_schema.COLUMNS'), result: [[{ column_count: 1 }]] },
|
||||
{ match: sql => sql.includes('information_schema.TABLES'), result: [[{ table_count: 0 }]] }
|
||||
]);
|
||||
const [migration] = await getPendingMigrations(pool, { currentVersion: '2.19.0' });
|
||||
const query = pool.query.bind(pool);
|
||||
pool.query = async function (sql, params) {
|
||||
if (sql.startsWith('SELECT id, content_json')) throw new Error('read failed');
|
||||
return query(sql, params);
|
||||
};
|
||||
|
||||
await assert.rejects(migration.run(pool), /read failed/);
|
||||
assert.deepEqual(pool.queries.slice(-2), [['ROLLBACK'], ['RELEASE CONNECTION']]);
|
||||
assert.ok(!pool.queries.some(([sql]) => sql === 'COMMIT'));
|
||||
});
|
||||
Reference in new issue
Block a user