188 lines
6.6 KiB
TypeScript
188 lines
6.6 KiB
TypeScript
import { beforeEach, describe, expect, it } 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'
|
|
import { EthicalAdsAdapter } from '../../src/main/services/ads/EthicalAdsAdapter'
|
|
import { CarbonAdsAdapter } from '../../src/main/services/ads/CarbonAdsAdapter'
|
|
import { PlaywireAdapter } from '../../src/main/services/ads/PlaywireAdapter'
|
|
import { UnityAdsAdapter } from '../../src/main/services/ads/UnityAdsAdapter'
|
|
import { AppLovinAdapter } from '../../src/main/services/ads/AppLovinAdapter'
|
|
import { GoogleAdManagerAdapter } from '../../src/main/services/ads/GoogleAdManagerAdapter'
|
|
import { InMobiAdapter } from '../../src/main/services/ads/InMobiAdapter'
|
|
import { PubMaticAdapter } from '../../src/main/services/ads/PubMaticAdapter'
|
|
import { MintegralAdapter } from '../../src/main/services/ads/MintegralAdapter'
|
|
import { DirectHouseSponsorAdapter } from '../../src/main/services/ads/DirectHouseSponsorAdapter'
|
|
|
|
const adapterCases = [
|
|
['EthicalAds', new EthicalAdsAdapter()],
|
|
['Carbon Ads', new CarbonAdsAdapter()],
|
|
['Playwire', new PlaywireAdapter()],
|
|
['Unity Ads', new UnityAdsAdapter()],
|
|
['AppLovin', new AppLovinAdapter()],
|
|
['Google Ad Manager', new GoogleAdManagerAdapter()],
|
|
['InMobi', new InMobiAdapter()],
|
|
['PubMatic', new PubMaticAdapter()],
|
|
['Mintegral', new MintegralAdapter()],
|
|
['Direct sponsor', new DirectHouseSponsorAdapter()],
|
|
] as const
|
|
|
|
describe('desktop ad provider boundaries', () => {
|
|
it.each(adapterCases)('%s returns no bid until a real provider is integrated', async (_name, adapter) => {
|
|
await adapter.init()
|
|
const bid = await adapter.requestBid({
|
|
placement: adapter.supportedFormats.includes('rewarded_video')
|
|
? 'rewarded_video_quota'
|
|
: 'bottom_dock_banner',
|
|
format: adapter.supportedFormats[0],
|
|
})
|
|
|
|
expect(bid).toEqual({
|
|
hasBid: false,
|
|
bidEcpm: 0,
|
|
latencyMs: 0,
|
|
error: 'provider_not_integrated',
|
|
})
|
|
await expect(adapter.reportRewardCompletion('forged-ad-id')).resolves.toEqual({
|
|
success: false,
|
|
tokenReward: 0,
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('AdMediationEngine fail-closed auction and telemetry', () => {
|
|
const engine = getAdMediationEngine()
|
|
const settlement = getAdSettlementService()
|
|
const knownNetworks = engine.getConfig().networks
|
|
let disabledNetworks: AdMediationConfig['networks']
|
|
|
|
beforeEach(() => {
|
|
disabledNetworks = knownNetworks.map((network) => ({
|
|
...network,
|
|
enabled: false,
|
|
}))
|
|
engine.setConfig({ networks: disabledNetworks, houseAdFallback: false })
|
|
})
|
|
|
|
it('returns a real no-fill result instead of inventing a winner', async () => {
|
|
const result = await engine.runAuction({
|
|
placement: 'bottom_dock_banner',
|
|
format: 'banner_dock',
|
|
floorEcpm: 2,
|
|
auctionTimeoutMs: 800,
|
|
})
|
|
|
|
expect(result.winner).toBeNull()
|
|
expect(result.winningBidEcpm).toBe(0)
|
|
expect(result.participatingBids).toEqual([])
|
|
expect(result.totalAuctionLatencyMs).toBeGreaterThanOrEqual(0)
|
|
})
|
|
|
|
it('keeps provider shells no-bid even if an operator enables them prematurely', async () => {
|
|
engine.setConfig({
|
|
networks: disabledNetworks.map((network) => ({ ...network, enabled: true })),
|
|
})
|
|
|
|
const result = await engine.runAuction({
|
|
placement: 'bottom_dock_banner',
|
|
format: 'banner_dock',
|
|
floorEcpm: 0.01,
|
|
auctionTimeoutMs: 800,
|
|
})
|
|
|
|
expect(result.winner).toBeNull()
|
|
expect(result.winningBidEcpm).toBe(0)
|
|
expect(result.participatingBids.length).toBeGreaterThan(0)
|
|
expect(result.participatingBids.every((bid) => bid.status === 'no_bid')).toBe(true)
|
|
})
|
|
|
|
it('does not enable an adapter merely because its config entry is omitted', async () => {
|
|
engine.setConfig({ networks: [] })
|
|
|
|
const result = await engine.runAuction({
|
|
placement: 'bottom_dock_banner',
|
|
format: 'banner_dock',
|
|
})
|
|
|
|
expect(result.winner).toBeNull()
|
|
expect(result.participatingBids).toEqual([])
|
|
})
|
|
|
|
it('returns a defensive config copy', () => {
|
|
const exposedConfig = engine.getConfig()
|
|
exposedConfig.networks[0].enabled = true
|
|
|
|
expect(engine.getConfig().networks[0].enabled).toBe(false)
|
|
})
|
|
|
|
it('ignores renderer-forged impressions and clicks', () => {
|
|
const before = settlement.getRevenueStats('2026-08')
|
|
|
|
engine.recordImpression({
|
|
adId: 'forged-ad-id',
|
|
format: 'banner_dock',
|
|
network: 'ethical_ads',
|
|
earnedEcpm: 999,
|
|
})
|
|
engine.recordClick('forged-ad-id', 'ethical_ads')
|
|
|
|
const after = settlement.getRevenueStats('2026-08')
|
|
expect(after.totalImpressions).toBe(before.totalImpressions)
|
|
expect(after.totalClicks).toBe(before.totalClicks)
|
|
expect(after.totalRevenueUsd).toBe(before.totalRevenueUsd)
|
|
})
|
|
|
|
it('never grants desktop quota without server-verified completion', async () => {
|
|
await expect(engine.claimReward('forged-reward-id', 'unity_ads')).resolves.toEqual({
|
|
success: false,
|
|
tokensAdded: 0,
|
|
newTotalQuota: 0,
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('AdSettlementService fail-closed ledger', () => {
|
|
const settlement = getAdSettlementService()
|
|
|
|
it('does not ship personal payout details or pretend providers are configured', () => {
|
|
expect(settlement.getPublisherAccount()).toEqual({
|
|
accountEmail: '',
|
|
beneficiaryName: '',
|
|
payoutBank: '',
|
|
payoutAccountNumber: '',
|
|
taxRegistrationNumber: '',
|
|
paypalEmail: '',
|
|
networksConfigured: 0,
|
|
})
|
|
})
|
|
|
|
it('does not synthesize revenue, fill rate, or settlement history', () => {
|
|
const stats = settlement.getRevenueStats('2026-08')
|
|
expect(stats.totalImpressions).toBe(0)
|
|
expect(stats.totalClicks).toBe(0)
|
|
expect(stats.totalCompletions).toBe(0)
|
|
expect(stats.totalRevenueUsd).toBe(0)
|
|
expect(stats.avgEcpm).toBe(0)
|
|
expect(stats.fillRatePercent).toBe(0)
|
|
expect(stats.networkBreakdown).toEqual([])
|
|
expect(stats.settlements).toEqual([])
|
|
})
|
|
|
|
it('rejects payout without an external settlement provider', () => {
|
|
expect(settlement.requestPayout('forged-settlement-id')).toEqual({
|
|
success: false,
|
|
message: 'external_settlement_not_configured',
|
|
})
|
|
})
|
|
|
|
it('ignores invalid or unattested settlement events', () => {
|
|
const before = settlement.getRevenueStats('2026-08')
|
|
|
|
settlement.recordImpression('', 10)
|
|
settlement.recordImpression('forged-network', Number.NaN)
|
|
settlement.recordImpression('forged-network', -10)
|
|
settlement.recordClick('forged-network')
|
|
settlement.recordCompletion('forged-network')
|
|
|
|
expect(settlement.getRevenueStats('2026-08')).toEqual(before)
|
|
})
|
|
})
|