fix(favorites): geaenderte Symbol-Adresse wird sofort angezeigt

- eine neue, ausdruecklich eingetragene Logo-Adresse verdraengt ein frueher
  hochgeladenes Symbol (Vorrang der Datei liess die Adresse unsichtbar)
- iconVersion steigt mit, die Kachel laedt das Bild neu
- Regressionstests: Upload wird ersetzt, unveraenderte Adresse laesst Upload stehen

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-29 15:32:30 +02:00
parent 8c644de5da
commit 7188c5b958
2 changed files with 56 additions and 2 deletions
@@ -859,6 +859,40 @@ describe('FavoritesService — Bindung an forTenant() (260911-gwh)', () => {
}); });
}); });
describe('update — ausdrueckliche Logo-Adresse verdraengt ein hochgeladenes Symbol (260929-lh3)', () => {
const file = { buffer: PNG, originalname: 'x.png', mimetype: 'image/png', size: PNG.length };
it('neue, abweichende iconUrl bei vorhandenem Upload: Upload-Typ null, Datei weg, iconVersion erneut +1 — die neue Adresse wird angezeigt', async () => {
const prisma = makeFakePrisma([baseRow]);
const service = new FavoritesService(prisma as any, makeIconDiscovery() as any);
await service.uploadIcon('t1', 'f1', 'user-a1', file);
expect(fs.existsSync(fileFor('user-a1', 'f1', 'png'))).toBe(true);
const updated = await service.update('t1', 'f1', 'user-a1', {
iconUrl: 'https://neu.invalid/logo.png',
} as any);
expect(updated.iconUrl).toBe('https://neu.invalid/logo.png');
expect(updated.uploadedIconMime).toBeNull();
expect(updated.iconVersion).toBe(2);
expect(fs.existsSync(fileFor('user-a1', 'f1', 'png'))).toBe(false);
});
it('UNVERAENDERTE iconUrl bei vorhandenem Upload (das Formular schickt sie bei jedem Speichern mit): Upload bleibt', async () => {
const prisma = makeFakePrisma([baseRow]);
const service = new FavoritesService(prisma as any, makeIconDiscovery() as any);
await service.uploadIcon('t1', 'f1', 'user-a1', file);
const updated = await service.update('t1', 'f1', 'user-a1', {
iconUrl: baseRow.iconUrl,
} as any);
expect(updated.uploadedIconMime).toBe('image/png');
expect(updated.iconVersion).toBe(1);
expect(fs.existsSync(fileFor('user-a1', 'f1', 'png'))).toBe(true);
});
});
describe('remove() mit hochgeladenem Symbol', () => { describe('remove() mit hochgeladenem Symbol', () => {
it('Zeile und Datei weg', async () => { it('Zeile und Datei weg', async () => {
const prisma = makeFakePrisma([baseRow]); const prisma = makeFakePrisma([baseRow]);
+22 -2
View File
@@ -219,14 +219,34 @@ export class FavoritesService {
} }
} }
if (data.iconUrl !== undefined && data.iconUrl !== link.iconUrl) { // 260929-lh3: eine NEUE, ausdruecklich eingetragene Logo-Adresse muss
// Vorrang vor einem frueher hochgeladenen Symbol haben. `getIconBytes`
// liefert bei gesetztem `uploadedIconMime` IMMER die Datei — ohne diesen
// Schritt blieb die neue Adresse gespeichert, aber unsichtbar (die Kachel
// zeigte weiter das alte hochgeladene Bild). Nur bei einer tatsaechlichen
// Aenderung: das Formular schickt die unveraenderte Adresse bei jedem
// Speichern mit, das darf ein hochgeladenes Symbol nicht verdraengen.
const iconUrlChanged = data.iconUrl !== undefined && data.iconUrl !== link.iconUrl;
const explicitUrlReplacesUpload =
iconUrlChanged && Boolean(dto.iconUrl) && link.uploadedIconMime !== null;
if (explicitUrlReplacesUpload) {
data.uploadedIconMime = null;
}
if (iconUrlChanged) {
data.iconVersion = { increment: 1 }; data.iconVersion = { increment: 1 };
} }
return tenantPrisma.favoriteLink.update({ const updated = await tenantPrisma.favoriteLink.update({
where: { id }, where: { id },
data, data,
}); });
if (explicitUrlReplacesUpload && link.uploadedIconMime !== null) {
await this.removeIconFile(id, link.userId, link.uploadedIconMime, 'ersetzte');
}
return updated;
} }
/** /**