From 2fe6495c92e11c0d4e0f85928ca2cc707e818a72 Mon Sep 17 00:00:00 2001 From: William Valentin Date: Tue, 17 Feb 2026 10:46:54 -0800 Subject: [PATCH] test(channels): cover line and zalo minio upload fallback path --- docs/plans/state.json | 12 ++++++++++ src/channels/line/adapter.test.ts | 38 +++++++++++++++++++++++++++++++ src/channels/zalo/adapter.test.ts | 37 ++++++++++++++++++++++++++++++ 3 files changed, 87 insertions(+) diff --git a/docs/plans/state.json b/docs/plans/state.json index 42cc9e1..30c3e95 100644 --- a/docs/plans/state.json +++ b/docs/plans/state.json @@ -3712,6 +3712,18 @@ "docs/plans/state.json" ], "test_status": "pnpm test:run src/channels/line/adapter.test.ts src/channels/zalo/adapter.test.ts src/daemon/channels.test.ts + pnpm typecheck passing" + }, + "line-zalo-minio-failure-fallback-coverage": { + "status": "completed", + "date": "2026-02-17", + "updated": "2026-02-17", + "summary": "Added regression coverage for LINE/Zalo MinIO upload failure behavior to ensure adapters emit warning logs and still send explicit in-chat binary fallback notices when `mc` share operations fail.", + "files_modified": [ + "src/channels/line/adapter.test.ts", + "src/channels/zalo/adapter.test.ts", + "docs/plans/state.json" + ], + "test_status": "pnpm test:run src/channels/line/adapter.test.ts src/channels/zalo/adapter.test.ts + pnpm typecheck passing" } }, "overall_progress": { diff --git a/src/channels/line/adapter.test.ts b/src/channels/line/adapter.test.ts index 05f468e..b9ce485 100644 --- a/src/channels/line/adapter.test.ts +++ b/src/channels/line/adapter.test.ts @@ -146,6 +146,44 @@ describe('LineAdapter', () => { warnSpy.mockRestore(); }); + it('falls back to warning notice when MinIO upload fails', async () => { + const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); + const adapter = new LineAdapter({ + channelAccessToken: 'token', + channelSecret: 'secret', + minio: { + enabled: true, + endpoint: 'localhost:9000', + accessKey: 'minio', + secretKey: 'secret', + bucket: 'flynn', + prefix: 'channels/line', + secure: false, + }, + minioExecRunner: vi.fn(async () => { + throw new Error('mc unavailable'); + }), + }); + await adapter.connect(); + mockFetch.mockResolvedValue({ + ok: true, + status: 200, + text: async () => '', + } as Response); + + await adapter.send('U123', { + text: '', + attachments: [{ mimeType: 'image/png', data: 'aGVsbG8=', filename: 'file.png' }], + }); + + expect(mockFetch).toHaveBeenCalledTimes(1); + const body = JSON.parse(String(mockFetch.mock.calls[0]?.[1]?.body ?? '{}')); + expect(body.messages?.[0]?.text).toBe('[LINE] Binary attachment not uploaded yet: file.png (image/png).'); + expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('LINE: MinIO upload failed')); + expect(warnSpy).toHaveBeenCalledWith('LINE: skipping attachment data (image/png) — upload not implemented'); + warnSpy.mockRestore(); + }); + it('send delivers URL attachment even when text is empty', async () => { const adapter = new LineAdapter({ channelAccessToken: 'token', diff --git a/src/channels/zalo/adapter.test.ts b/src/channels/zalo/adapter.test.ts index b4de759..301c0d8 100644 --- a/src/channels/zalo/adapter.test.ts +++ b/src/channels/zalo/adapter.test.ts @@ -131,6 +131,43 @@ describe('ZaloAdapter', () => { warnSpy.mockRestore(); }); + it('falls back to warning notice when MinIO upload fails', async () => { + const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); + const adapter = new ZaloAdapter({ + oaAccessToken: 'token', + minio: { + enabled: true, + endpoint: 'localhost:9000', + accessKey: 'minio', + secretKey: 'secret', + bucket: 'flynn', + prefix: 'channels/zalo', + secure: false, + }, + minioExecRunner: vi.fn(async () => { + throw new Error('mc unavailable'); + }), + }); + await adapter.connect(); + mockFetch.mockResolvedValue({ + ok: true, + status: 200, + text: async () => '', + } as Response); + + await adapter.send('uid-1', { + text: '', + attachments: [{ mimeType: 'application/pdf', data: 'aGVsbG8=', filename: 'file.pdf' }], + }); + + expect(mockFetch).toHaveBeenCalledTimes(1); + const body = JSON.parse(String(mockFetch.mock.calls[0]?.[1]?.body ?? '{}')); + expect(body.message?.text).toBe('[Zalo] Binary attachment not uploaded yet: file.pdf (application/pdf).'); + expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('Zalo: MinIO upload failed')); + expect(warnSpy).toHaveBeenCalledWith('Zalo: skipping attachment data (application/pdf) — upload not implemented'); + warnSpy.mockRestore(); + }); + it('send delivers URL attachment even when text is empty', async () => { const adapter = new ZaloAdapter({ oaAccessToken: 'token' }); await adapter.connect();