From effaa2dc9dfcec88f176a3a1dde550c2ea8f7312 Mon Sep 17 00:00:00 2001 From: James Grugett Date: Mon, 11 May 2026 16:55:23 -0700 Subject: [PATCH] Fix ZeroClick ad display text --- .../ad-providers/__tests__/zeroclick.test.ts | 102 ++++++++++++++++++ web/src/lib/ad-providers/zeroclick.ts | 7 +- 2 files changed, 104 insertions(+), 5 deletions(-) create mode 100644 web/src/lib/ad-providers/__tests__/zeroclick.test.ts diff --git a/web/src/lib/ad-providers/__tests__/zeroclick.test.ts b/web/src/lib/ad-providers/__tests__/zeroclick.test.ts new file mode 100644 index 0000000000..67086972b9 --- /dev/null +++ b/web/src/lib/ad-providers/__tests__/zeroclick.test.ts @@ -0,0 +1,102 @@ +import { describe, expect, test } from 'bun:test' + +import { createZeroClickProvider } from '../zeroclick' + +import type { Logger } from '@codebuff/common/types/contracts/logger' + +const logger: Logger = { + debug: () => {}, + info: () => {}, + warn: () => {}, + error: () => {}, +} + +describe('ZeroClick ad provider', () => { + test('uses content as ad text and stores brand name as title', async () => { + const provider = createZeroClickProvider({ apiKey: 'test-key' }) + const fetch = Object.assign( + async () => + new Response( + JSON.stringify([ + { + id: 'offer-1', + title: + 'Long product title that should not be used as the display label', + subtitle: 'Subtitle that should not be included', + content: 'Main offer description.', + cta: 'Try it', + clickUrl: 'https://zeroclick.example/click', + brand: { + name: 'Acme', + url: null, + iconUrl: 'https://example.com/icon.png', + }, + }, + ]), + { + status: 200, + headers: { 'Content-Type': 'application/json' }, + }, + ), + { preconnect: () => {} }, + ) as typeof globalThis.fetch + + const result = await provider.fetchAd({ + userId: 'user-1', + userEmail: 'user@example.com', + clientIp: '127.0.0.1', + messages: [], + testMode: true, + logger, + fetch, + }) + + expect(result?.ads).toHaveLength(1) + expect(result?.ads[0]).toMatchObject({ + adText: 'Main offer description.', + title: 'Acme', + cta: 'Try it', + url: '', + favicon: 'https://example.com/icon.png', + clickUrl: 'https://zeroclick.example/click', + impressionIds: ['offer-1'], + }) + }) + + test('uses subtitle as ad text fallback when content is missing', async () => { + const provider = createZeroClickProvider({ apiKey: 'test-key' }) + const fetch = Object.assign( + async () => + new Response( + JSON.stringify([ + { + id: 'offer-1', + title: 'Long product title', + subtitle: 'Fallback subtitle description.', + content: null, + cta: 'Try it', + clickUrl: 'https://zeroclick.example/click', + brand: { name: 'Acme' }, + }, + ]), + { + status: 200, + headers: { 'Content-Type': 'application/json' }, + }, + ), + { preconnect: () => {} }, + ) as typeof globalThis.fetch + + const result = await provider.fetchAd({ + userId: 'user-1', + userEmail: 'user@example.com', + clientIp: '127.0.0.1', + messages: [], + testMode: true, + logger, + fetch, + }) + + expect(result?.ads[0]?.adText).toBe('Fallback subtitle description.') + }) +}) diff --git a/web/src/lib/ad-providers/zeroclick.ts b/web/src/lib/ad-providers/zeroclick.ts index af332cb938..4d4979cf61 100644 --- a/web/src/lib/ad-providers/zeroclick.ts +++ b/web/src/lib/ad-providers/zeroclick.ts @@ -66,14 +66,11 @@ function normalize(raw: ZeroClickOffer, servedId: string): NormalizedAd | null { if (!raw.id || !raw.clickUrl) return null const title = + raw.brand?.name?.trim() || raw.title?.trim() || raw.product?.title?.trim() || - raw.brand?.name?.trim() || 'Sponsored' - const content = [raw.subtitle, raw.content] - .map((part) => part?.trim()) - .filter(Boolean) - .join(' ') + const content = raw.content?.trim() || raw.subtitle?.trim() || '' return { adText: content || title,