ee54e9a1f4
Il commit 1522d97 ha spostato la freccia "→" dei bottoni promo nel CSS
(::after) e il maiuscolo in text-transform, cambiando il seed in minuscolo
senza freccia. Ma il seed è INSERT OR IGNORE: sui DB esistenti (produzione)
le righe restavano a "LE NOSTRE PROPOSTE →" / "ACQUISTA →", e il ::after
avrebbe prodotto una doppia freccia.
Aggiunge una migrazione guardata dal vecchio valore (idempotente, non tocca
le righe modificate dal pannello) che le allinea al seed corrente. Verificata
su copia del DB di produzione: frecce corrette e 40 edit del pannello intatti.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
152 lines
8.0 KiB
TypeScript
152 lines
8.0 KiB
TypeScript
import { describe, it, expect } from 'vitest';
|
|
import { mkdtempSync, rmSync } from 'node:fs';
|
|
import { tmpdir } from 'node:os';
|
|
import { join } from 'node:path';
|
|
import { createDb } from '../src/lib/db';
|
|
import { createUser } from '../src/lib/auth';
|
|
|
|
describe('schema content_blocks e ruoli', () => {
|
|
it('crea la tabella content_blocks', () => {
|
|
const db = createDb(':memory:');
|
|
const cols = db.prepare(`PRAGMA table_info(content_blocks)`).all() as { name: string }[];
|
|
expect(cols.map((c) => c.name)).toEqual(
|
|
expect.arrayContaining(['tag', 'type', 'value', 'styles', 'updated_at', 'updated_by'])
|
|
);
|
|
});
|
|
|
|
it('rifiuta type non valido', () => {
|
|
const db = createDb(':memory:');
|
|
expect(() =>
|
|
db.prepare(`INSERT INTO content_blocks (tag, type, value) VALUES ('x.y', 'video', '')`).run()
|
|
).toThrow();
|
|
});
|
|
|
|
it('users ha colonna role con default admin', () => {
|
|
const db = createDb(':memory:');
|
|
db.prepare(`INSERT INTO users (username, password_hash) VALUES ('a', 'h')`).run();
|
|
const row = db.prepare(`SELECT role FROM users WHERE username = 'a'`).get() as { role: string };
|
|
expect(row.role).toBe('admin');
|
|
});
|
|
|
|
it('migra i tag programmi → servizi preservando i valori modificati dal pannello', () => {
|
|
const dir = mkdtempSync(join(tmpdir(), 'il-db-test-'));
|
|
const path = join(dir, 'test.db');
|
|
try {
|
|
// Simula un DB di produzione pre-rinomina: tag vecchi con valori modificati dal pannello.
|
|
const db1 = createDb(path);
|
|
const old = db1.transaction(() => {
|
|
db1.prepare("DELETE FROM content_blocks WHERE tag IN ('home.services.title', 'service.rehab.title', 'services.hero.title', 'services.meta.title', 'footer.services.1.label', 'footer.col.services-title')").run();
|
|
const ins = db1.prepare("INSERT INTO content_blocks (tag, type, value, updated_by) VALUES (?, 'text', ?, 'editor')");
|
|
ins.run('home.programs.title', 'Titolo modificato');
|
|
ins.run('program.rehab.title', 'Rehab modificato');
|
|
ins.run('programs.hero.title', 'Hero modificato');
|
|
ins.run('footer.programs.1.label', 'Label modificata');
|
|
ins.run('footer.col.programs-title', 'Colonna modificata');
|
|
// Riga mai toccata dal pannello: valore identico al vecchio seed → deve prendere il nuovo default.
|
|
db1.prepare("INSERT INTO content_blocks (tag, type, value) VALUES ('programs.meta.title', 'text', 'Programmi')").run();
|
|
db1.prepare("UPDATE content_blocks SET value = 'Programmi' WHERE tag = 'global.nav.2.label'").run();
|
|
});
|
|
old();
|
|
db1.close();
|
|
|
|
// Riapertura: la migrazione rinomina i tag preservando valore e updated_by.
|
|
const db2 = createDb(path);
|
|
const val = (tag: string) =>
|
|
db2.prepare('SELECT value, updated_by FROM content_blocks WHERE tag = ?').get(tag) as { value: string; updated_by: string | null } | undefined;
|
|
expect(val('home.services.title')).toMatchObject({ value: 'Titolo modificato', updated_by: 'editor' });
|
|
expect(val('service.rehab.title')).toMatchObject({ value: 'Rehab modificato', updated_by: 'editor' });
|
|
expect(val('services.hero.title')).toMatchObject({ value: 'Hero modificato', updated_by: 'editor' });
|
|
expect(val('footer.services.1.label')).toMatchObject({ value: 'Label modificata', updated_by: 'editor' });
|
|
expect(val('footer.col.services-title')).toMatchObject({ value: 'Colonna modificata', updated_by: 'editor' });
|
|
|
|
// Le righe rimaste al vecchio valore di default prendono il nuovo ("Servizi").
|
|
// Dopo il riordino del menu (commit 5ff4771) la voce "Servizi" è la 2ª: la migrazione
|
|
// aggiorna global.nav.2.label da 'Programmi' al seed corrente 'Servizi'.
|
|
expect(val('services.meta.title')).toMatchObject({ value: 'Servizi' });
|
|
expect(val('global.nav.2.label')).toMatchObject({ value: 'Servizi' });
|
|
// La riga rinominata ma modificata dal pannello resta invariata (già verificata sopra):
|
|
// home.services.title = 'Titolo modificato'.
|
|
|
|
// Nessun tag vecchio residuo.
|
|
const residui = db2.prepare(
|
|
"SELECT count(*) AS c FROM content_blocks WHERE tag LIKE 'program%' OR tag LIKE 'home.programs%' OR tag LIKE 'footer.programs%' OR tag = 'footer.col.programs-title'"
|
|
).get() as { c: number };
|
|
expect(residui.c).toBe(0);
|
|
|
|
// Idempotente: una terza apertura non altera i valori.
|
|
db2.close();
|
|
const db3 = createDb(path);
|
|
const again = db3.prepare('SELECT value FROM content_blocks WHERE tag = ?').get('home.services.title') as { value: string };
|
|
expect(again.value).toBe('Titolo modificato');
|
|
db3.close();
|
|
} finally {
|
|
rmSync(dir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
it('allinea i bottoni promo col vecchio testo-freccia al seed senza freccia', () => {
|
|
const dir = mkdtempSync(join(tmpdir(), 'il-db-promo-'));
|
|
const path = join(dir, 'test.db');
|
|
try {
|
|
// DB pre-refactor: valori con freccia letterale e maiuscolo (poi spostati nel CSS).
|
|
const db1 = createDb(path);
|
|
db1.prepare("UPDATE content_blocks SET value = 'LE NOSTRE PROPOSTE →' WHERE tag = 'promo.hero.cta'").run();
|
|
db1.prepare("UPDATE content_blocks SET value = 'ACQUISTA →' WHERE tag = 'promo.buy-label'").run();
|
|
// Riga modificata dal pannello: NON deve essere toccata.
|
|
db1.prepare("UPDATE content_blocks SET value = 'Compra ora', updated_by = 'editor' WHERE tag = 'promo.buy-label'").run();
|
|
db1.close();
|
|
|
|
// Riapertura: la migrazione allinea solo le righe rimaste al vecchio default con freccia.
|
|
const db2 = createDb(path);
|
|
const val = (tag: string) => (db2.prepare('SELECT value FROM content_blocks WHERE tag = ?').get(tag) as { value: string }).value;
|
|
expect(val('promo.hero.cta')).toBe('Le nostre proposte');
|
|
// promo.buy-label era stato editato dal pannello → resta invariato.
|
|
expect(val('promo.buy-label')).toBe('Compra ora');
|
|
db2.close();
|
|
|
|
// Idempotente: terza apertura non altera.
|
|
const db3 = createDb(path);
|
|
expect((db3.prepare("SELECT value FROM content_blocks WHERE tag = 'promo.hero.cta'").get() as { value: string }).value).toBe('Le nostre proposte');
|
|
db3.close();
|
|
} finally {
|
|
rmSync(dir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
it('migra un DB esistente senza colonna role', () => {
|
|
// simula DB vecchio: createDb due volte sullo stesso path in-memory non è possibile,
|
|
// quindi si verifica che la migrazione sia idempotente (doppia esecuzione non lancia)
|
|
const db = createDb(':memory:');
|
|
expect(() => createDb(':memory:')).not.toThrow();
|
|
expect(db.prepare(`PRAGMA table_info(users)`).all().length).toBeGreaterThanOrEqual(4);
|
|
});
|
|
});
|
|
|
|
describe('migrazioni ruoli/guid/autore', () => {
|
|
it('ogni content_block ha un guid non vuoto e univoco', () => {
|
|
const db = createDb(':memory:');
|
|
const rows = db.prepare('SELECT guid FROM content_blocks').all() as { guid: string | null }[];
|
|
expect(rows.length).toBeGreaterThan(100);
|
|
expect(rows.every((r) => typeof r.guid === 'string' && r.guid!.length >= 10)).toBe(true);
|
|
const guids = new Set(rows.map((r) => r.guid));
|
|
expect(guids.size).toBe(rows.length);
|
|
});
|
|
|
|
it('lo statement di migrazione unifica editor in superuser', () => {
|
|
// La migrazione gira dentro createDb, ma :memory: non persiste tra due boot: qui
|
|
// verifichiamo l'invariante SQL applicata dalla migrazione (editor → superuser).
|
|
// L'end-to-end su DB pre-esistente è coperto dallo smoke di produzione (Task 15).
|
|
const db = createDb(':memory:');
|
|
db.prepare("INSERT INTO users (username, password_hash, role) VALUES ('vecchio', 'x', 'editor')").run();
|
|
db.prepare("UPDATE users SET role = 'superuser' WHERE role = 'editor'").run();
|
|
const row = db.prepare("SELECT role FROM users WHERE username = 'vecchio'").get() as { role: string };
|
|
expect(row.role).toBe('superuser');
|
|
});
|
|
|
|
it('posts ha la colonna author_id', () => {
|
|
const db = createDb(':memory:');
|
|
const cols = db.prepare('PRAGMA table_info(posts)').all() as { name: string }[];
|
|
expect(cols.some((c) => c.name === 'author_id')).toBe(true);
|
|
});
|
|
});
|