feat(desktop): fill sponsor slots from direct house campaigns
When mediation had no programmatic fill, the banner and rewarded surfaces collapsed to empty space. Direct house sponsors now serve their own copy and click-through, with the same settlement accounting used by the mediated network, and the mediation engine tests cover the added path.
This commit is contained in:
parent
911c9f0229
commit
c8d802d78f
5 changed files with 363 additions and 61 deletions
|
|
@ -1,4 +1,4 @@
|
|||
import { beforeEach, describe, expect, it } from 'vitest'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import type { AdMediationConfig } from '@d3ro/core/types'
|
||||
import { getAdMediationEngine } from '../../src/main/services/ads/AdMediationEngine'
|
||||
import { getAdSettlementService } from '../../src/main/services/ads/AdSettlementService'
|
||||
|
|
@ -23,7 +23,6 @@ const adapterCases = [
|
|||
['InMobi', new InMobiAdapter()],
|
||||
['PubMatic', new PubMaticAdapter()],
|
||||
['Mintegral', new MintegralAdapter()],
|
||||
['Direct sponsor', new DirectHouseSponsorAdapter()],
|
||||
] as const
|
||||
|
||||
describe('desktop ad provider boundaries', () => {
|
||||
|
|
@ -49,6 +48,96 @@ describe('desktop ad provider boundaries', () => {
|
|||
})
|
||||
})
|
||||
|
||||
describe('DirectHouseSponsorAdapter live decision endpoint', () => {
|
||||
it('is fail-closed until an endpoint is configured', async () => {
|
||||
const adapter = new DirectHouseSponsorAdapter()
|
||||
await adapter.init()
|
||||
const bid = await adapter.requestBid({ placement: 'bottom_dock_banner', format: 'banner_dock' })
|
||||
|
||||
expect(bid).toEqual({
|
||||
hasBid: false,
|
||||
bidEcpm: 0,
|
||||
latencyMs: 0,
|
||||
error: 'adapter_not_configured',
|
||||
})
|
||||
await expect(adapter.reportRewardCompletion('forged-ad-id')).resolves.toEqual({
|
||||
success: false,
|
||||
tokenReward: 0,
|
||||
})
|
||||
})
|
||||
|
||||
it('returns a validated live bid from a configured endpoint', async () => {
|
||||
const adapter = new DirectHouseSponsorAdapter()
|
||||
await adapter.init({ endpointUrl: 'https://ads.example.com', apiSecret: 'secret' })
|
||||
const fetchMock = vi.fn().mockResolvedValue(
|
||||
new Response(
|
||||
JSON.stringify({
|
||||
bidEcpm: 9.5,
|
||||
creative: {
|
||||
id: 'creative-1',
|
||||
title: 'Sponsor',
|
||||
advertiserName: 'Example Inc',
|
||||
clickUrl: 'https://example.com/offer',
|
||||
format: 'banner_dock',
|
||||
},
|
||||
}),
|
||||
{ status: 200 },
|
||||
),
|
||||
)
|
||||
vi.stubGlobal('fetch', fetchMock)
|
||||
try {
|
||||
const bid = await adapter.requestBid({
|
||||
placement: 'bottom_dock_banner',
|
||||
format: 'banner_dock',
|
||||
floorEcpm: 2,
|
||||
})
|
||||
|
||||
expect(bid.hasBid).toBe(true)
|
||||
expect(bid.bidEcpm).toBe(9.5)
|
||||
expect(bid.creative?.networkId).toBe('direct_sponsor')
|
||||
expect(bid.creative?.clickUrl).toBe('https://example.com/offer')
|
||||
const [url, init] = fetchMock.mock.calls[0] as [string, RequestInit]
|
||||
expect(String(url)).toBe('https://ads.example.com/bid')
|
||||
expect(init.headers).toMatchObject({ Authorization: 'Bearer secret' })
|
||||
} finally {
|
||||
vi.unstubAllGlobals()
|
||||
}
|
||||
})
|
||||
|
||||
it('rejects a creative whose click target is not https', async () => {
|
||||
const adapter = new DirectHouseSponsorAdapter()
|
||||
await adapter.init({ endpointUrl: 'https://ads.example.com' })
|
||||
vi.stubGlobal(
|
||||
'fetch',
|
||||
vi.fn().mockResolvedValue(
|
||||
new Response(
|
||||
JSON.stringify({
|
||||
bidEcpm: 9.5,
|
||||
creative: {
|
||||
id: 'c',
|
||||
title: 't',
|
||||
advertiserName: 'a',
|
||||
clickUrl: 'http://insecure.example.com',
|
||||
format: 'banner_dock',
|
||||
},
|
||||
}),
|
||||
{ status: 200 },
|
||||
),
|
||||
),
|
||||
)
|
||||
try {
|
||||
const bid = await adapter.requestBid({
|
||||
placement: 'bottom_dock_banner',
|
||||
format: 'banner_dock',
|
||||
})
|
||||
expect(bid.hasBid).toBe(false)
|
||||
expect(bid.error).toBe('invalid_creative')
|
||||
} finally {
|
||||
vi.unstubAllGlobals()
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('AdMediationEngine fail-closed auction and telemetry', () => {
|
||||
const engine = getAdMediationEngine()
|
||||
const settlement = getAdSettlementService()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue