diff --git a/lib/addons/prebid/analytics.test.ts b/lib/addons/prebid/analytics.test.ts index cd457c41..a827c1cc 100644 --- a/lib/addons/prebid/analytics.test.ts +++ b/lib/addons/prebid/analytics.test.ts @@ -543,6 +543,40 @@ describe("OptablePrebidAnalytics", () => { ); }); + it("should report not missed but not loaded when a replayed auction carries Optable sources", async () => { + const auctionEndEvent = { + auctionId: "auction-replayed-cache-read", + timeout: 3000, + bidderRequests: [ + { + bidderCode: "bidder1", + bidderRequestId: "req-1", + ortb2: { + site: { domain: "example.com" }, + user: { + eids: [{ inserter: "optable.co", matcher: "matcher1", source: "source1" }], + }, + }, + bids: [], + }, + ], + bidsReceived: [], + noBids: [], + timeoutBids: [], + }; + + await analytics.trackAuctionEnd(auctionEndEvent, true); + await jest.runAllTimersAsync(); + + expect(mockOptableInstance.witness).toHaveBeenCalledWith( + "optable.prebid.auction", + expect.objectContaining({ + missed: false, + optableLoaded: false, + }) + ); + }); + it("should accumulate multiple bidWon events and emit one witness with an array", async () => { const auctionId = "auction-multi-unit"; const auctionEndEvent = { @@ -1132,6 +1166,51 @@ describe("OptablePrebidAnalytics", () => { analytics = new OptablePrebidAnalytics(mockOptableInstance); }); + it("should not mark a replayed auction as missed when Optable sources are present", async () => { + const auctionEndEvent = { + auctionId: "auction-replayed-with-cache", + bidderRequests: [ + { + bidderCode: "bidder1", + bidderRequestId: "req-1", + ortb2: { + site: { domain: "example.com" }, + user: { + eids: [{ inserter: "optable.co", matcher: "matcher1", source: "source1" }], + }, + }, + bids: [], + }, + ], + bidsReceived: [], + noBids: [], + }; + + const result = await analytics.toWitness(auctionEndEvent, [], true); + + expect(result.missed).toBe(false); + }); + + it("should keep a replayed auction as missed when no Optable sources are present", async () => { + const auctionEndEvent = { + auctionId: "auction-replayed-no-cache", + bidderRequests: [ + { + bidderCode: "bidder1", + bidderRequestId: "req-1", + ortb2: { site: { domain: "example.com" } }, + bids: [], + }, + ], + bidsReceived: [], + noBids: [], + }; + + const result = await analytics.toWitness(auctionEndEvent, [], true); + + expect(result.missed).toBe(true); + }); + it("should handle multiple bidder requests", async () => { const auctionEndEvent = { auctionId: "auction-multi", diff --git a/lib/addons/prebid/analytics.ts b/lib/addons/prebid/analytics.ts index bec0197c..cd4190b8 100644 --- a/lib/addons/prebid/analytics.ts +++ b/lib/addons/prebid/analytics.ts @@ -483,7 +483,7 @@ class OptablePrebidAnalytics { adUnitCode: e.adUnitCode, cpm: e.cpm, })), - missed, + missed: missed && oSourcesSet.size === 0, url: `${window.location.hostname}${window.location.pathname}`, tenant: this.optableInstance?.dcn?.node ?? "unknown", // eslint-disable-next-line no-undef diff --git a/lib/addons/prototypes/analytics.js b/lib/addons/prototypes/analytics.js index fbdf4f1e..db300ae3 100644 --- a/lib/addons/prototypes/analytics.js +++ b/lib/addons/prototypes/analytics.js @@ -274,7 +274,7 @@ class OptablePrebidAnalytics { optableMatchers: Array.from(oMatchersSet), optableSources: Array.from(oSourcesSet), liveintentEIDs: Array.from(lSourcesSet), - missed, + missed: missed && oSourcesSet.size === 0, url: `${window.location.hostname}${window.location.pathname}`, tenant: this.config.tenant, optableWrapperVersion: SDK_WRAPPER_VERSION, // eslint-disable-line no-undef diff --git a/lib/addons/prototypes/analytics.missed.test.js b/lib/addons/prototypes/analytics.missed.test.js new file mode 100644 index 00000000..6dd548dd --- /dev/null +++ b/lib/addons/prototypes/analytics.missed.test.js @@ -0,0 +1,41 @@ +import OptablePrebidAnalytics from "./analytics"; + +global.SDK_WRAPPER_VERSION = "1.0.0-test"; + +function bidderRequest(bidderCode, sources) { + return { + bidderCode, + bidderRequestId: `br-${bidderCode}`, + ortb2: { + user: { ext: { eids: sources.map((source) => ({ source, inserter: "optable.co", uids: [{ id: "x" }] })) } }, + }, + bids: [{ bidId: `bid-${bidderCode}`, adUnitCode: "slot-1" }], + }; +} + +async function run(sources, missed) { + const witness = jest.fn().mockResolvedValue(undefined); + const analytics = new OptablePrebidAnalytics({ witness }, { analytics: true, tenant: "t" }); + await analytics.trackAuctionEnd({ auctionId: "a-1", bidderRequests: [bidderRequest("a", sources)] }, missed); + return JSON.parse(witness.mock.calls[0][1].auction); +} + +beforeEach(() => { + window.optable = {}; +}); + +describe("missed", () => { + test("false when the hook attached before the auction", async () => { + expect((await run([], false)).missed).toBe(false); + }); + + test("false when the hook attached late but Optable sources are present", async () => { + const p = await run(["uidapi.com"], true); + expect(p.optableSources).toEqual(["uidapi.com"]); + expect(p.missed).toBe(false); + }); + + test("true when the hook attached late and no Optable sources are present", async () => { + expect((await run([], true)).missed).toBe(true); + }); +});