fix(logos): hochgeladenes Logo trägt die Datei-ID der Datei und die ID des Dienstes

Die API vergibt die Zeilen-ID des Logos jetzt selbst (Hash aus Konto und Datei, nicht mehr die Datei-ID). uploadPreparedLogo baute das neue Logo
aber noch mit `fileId: id`: das Bild des frisch hochgeladenen Logos wäre aus einer Datei geladen worden, die es nicht gibt.

- upload-logo.ts: `id` aus der Antwort der API, `fileId` aus der angelegten Datei (`$id`). Kommentare ohne „Zeilen-ID = Datei-ID“ (auch logo-api.ts).
- Test: Dienst-ID und Datei-ID sind verschieden und werden nicht vertauscht (Fakes und Standard-Abhängigkeiten).
- Sonst nirgends in web/src: Auswahl und Karten-Konfiguration nehmen `logo.id`, das Bild kommt über `logo.fileId` (loadLogoBlob), beides lief schon getrennt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Opus 5.5 committed 2026-10-05 05:08:39 +00:00
1 parent 6d152c11b3
commit 738358fcac
3 files changed
+24 -11

No files matched your search

+18 -6
View File
@@ -36,7 +36,7 @@ const form: LogoForm = { name: 'Beispielbank', displayName: 'Kenso', mono: false
type Call = { name: string; args: unknown[] };
/** Fake-Abhängigkeiten, die jeden Aufruf in Reihenfolge aufzeichnen; die hochgeladene Datei bekommt die ID f1. */
/** Fake-Abhängigkeiten, die jeden Aufruf in Reihenfolge aufzeichnen; die hochgeladene Datei bekommt die ID f1, das Logo der Dienst die Zeilen-ID r1. */
function fakeDeps(overrides: Partial<UploadLogoDeps> = {}) {
const calls: Call[] = [];
const log = <T>(name: string, args: unknown[], result: T): T => {
@@ -47,7 +47,7 @@ function fakeDeps(overrides: Partial<UploadLogoDeps> = {}) {
userId: () => log('userId', [], 'u1'),
createFile: async (file, permissions) => log('createFile', [file, permissions], { $id: 'f1' }),
deleteFile: async (id) => log('deleteFile', [id], undefined),
uploadLogo: async (input) => log('uploadLogo', [input], { id: 'f1' }),
uploadLogo: async (input) => log('uploadLogo', [input], { id: 'r1' }),
...overrides,
};
return {
@@ -100,11 +100,11 @@ describe('uploadPreparedLogo', () => {
expect(logo.mono).toBe(sent);
});
it('Erfolg: das neue Logo (ID = Datei-ID, mein Konto, ohne Herzen und Meldungen), nichts wird gelöscht', async () => {
it('Erfolg: das neue Logo (ID des Dienstes, Datei-ID der hochgeladenen Datei, mein Konto, ohne Herzen und Meldungen), nichts wird gelöscht', async () => {
const fake = fakeDeps();
const logo = await uploadPreparedLogo(prepared(), { name: ' Beispielbank ', displayName: 'Kenso', mono: true }, fake.deps);
expect(logo).toEqual({
id: 'f1',
id: 'r1',
name: 'Beispielbank',
displayName: 'Kenso',
fileId: 'f1',
@@ -119,6 +119,16 @@ describe('uploadPreparedLogo', () => {
expect(fake.of('deleteFile')).toHaveLength(0);
});
it('die ID kommt aus der Antwort des Dienstes, die fileId aus der angelegten Datei: beide sind verschieden und werden nie vertauscht', async () => {
const fake = fakeDeps({
createFile: async () => ({ $id: 'datei-9' }),
uploadLogo: async () => ({ id: 'zeile-7' }),
});
const logo = await uploadPreparedLogo(prepared(), form, fake.deps);
expect(logo.id).toBe('zeile-7');
expect(logo.fileId).toBe('datei-9');
});
describe('Fehler der API: gelöscht wird nur bei eindeutiger Ablehnung', () => {
it.each([400, 401, 403, 404])(
'eindeutig abgelehnt (%i): die Datei dieses Versuchs wird gelöscht, der Fehler kommt an',
@@ -239,7 +249,8 @@ describe('Standard-Abhängigkeiten (Appwrite-Bucket logo-images)', () => {
// Wie Appwrite: die Datei hat die ID, mit der sie angelegt wurde.
mocks.createFile.mockImplementation(async ({ fileId }: { fileId: string }) => ({ $id: fileId }));
mocks.deleteFile.mockResolvedValue({});
mocks.uploadLogo.mockImplementation(async ({ fileId }: { fileId: string }) => ({ id: fileId }));
// Wie der Dienst: die Zeile des Logos bekommt eine eigene ID, nicht die der Datei.
mocks.uploadLogo.mockImplementation(async ({ fileId }: { fileId: string }) => ({ id: `zeile-${fileId}` }));
});
it('lädt mit frischer ID in den Logo-Bucket und ruft die API mit genau dieser ID', async () => {
@@ -256,7 +267,8 @@ describe('Standard-Abhängigkeiten (Appwrite-Bucket logo-images)', () => {
expect(file.name).toBe('logo.png');
expect(permissions).toEqual(['read("any")', 'delete("user:u1")']);
expect(mocks.uploadLogo).toHaveBeenCalledWith({ name: 'Beispielbank', displayName: 'Kenso', mono: false, fileId });
expect(logo.id).toBe(fileId);
expect(logo.id).toBe(`zeile-${fileId}`);
expect(logo.fileId).toBe(fileId);
expect(mocks.deleteFile).not.toHaveBeenCalled();
});