import createS3Stub from "../../mocks/s3.mock"; import * as sinon from "sinon"; import CallToActionHashMapFake from "../fake/targeting/CallToActionHashMapFake"; import { NormStats } from "../../../src/services/scoringSystem/abstractScoreHashMap.service"; import { FakeCampaign } from "../fake/Campaign"; import { CampaignCreative } from "../../../src/steps/ordering/ordering.model"; const chai = require("chai"); const expect = chai.expect; describe("ctaHashMap Service", () => { let s3Stub: sinon.SinonStub; let expectedCtaScores: Map; let expectedCtaNormStats: Map; before(() => { expectedCtaScores = new Map([ ["INT|s1|c1|a1|au1", 0.7], ["INT|s1|c2|a1|-1", 0.6], ["INT|s1|c2|-1|-1", 0.5], ["MPU|-1|-1|-1|-1", 0.4], ["INT|-1|-1|-1|-1", 0.3], ]); expectedCtaNormStats = new Map([ [ "INT|c1", { bucketsMap: [ { inputUpper: 0.1, outputLower: 0, outputUpper: 0.76, }, { inputUpper: 0.785, outputLower: 0.76, outputUpper: 0.91, }, { inputUpper: 1, outputLower: 0.91, outputUpper: 1, }, ], }, ], [ "INT|c5", { bucketsMap: [ { inputUpper: 0.1, outputLower: 0, outputUpper: 0.76, }, { inputUpper: 0.785, outputLower: 0.76, outputUpper: 0.92, }, { inputUpper: 1, outputLower: 0.92, outputUpper: 1, }, ], }, ], [ "INT|c2", { bucketsMap: [ { inputUpper: 0.1, outputLower: 0, outputUpper: 0.76, }, { inputUpper: 0.785, outputLower: 0.76, outputUpper: 0.93, }, { inputUpper: 1, outputLower: 0.93, outputUpper: 1, }, ], }, ], [ "INT|-1", { bucketsMap: [ { inputUpper: 0.1, outputLower: 0, outputUpper: 0.76, }, { inputUpper: 0.785, outputLower: 0.76, outputUpper: 0.94, }, { inputUpper: 1, outputLower: 0.94, outputUpper: 1, }, ], }, ], [ "MPU|-1", { bucketsMap: [ { inputUpper: 0.1, outputLower: 0, outputUpper: 0.76, }, { inputUpper: 0.785, outputLower: 0.76, outputUpper: 0.95, }, { inputUpper: 1, outputLower: 0.95, outputUpper: 1, }, ], }, ], [ "-1|-1", { bucketsMap: [ { inputUpper: 0.1, outputLower: 0, outputUpper: 0.76, }, { inputUpper: 0.785, outputLower: 0.76, outputUpper: 0.96, }, { inputUpper: 1, outputLower: 0.96, outputUpper: 1, }, ], }, ], ]); }); beforeEach(() => { s3Stub = createS3Stub(); }); afterEach(() => { s3Stub.restore(); }); function checkCtaScores(cta: CallToActionHashMapFake) { const ctaMaps = cta.getMaps(); expect(ctaMaps.scoresMap).to.deep.equal(expectedCtaScores); expect(ctaMaps.normMap).to.deep.equal(expectedCtaNormStats); } describe("refresh", () => { it("should load scores with the correct values", async () => { const cta = new CallToActionHashMapFake(); await cta.refreshMaps(); checkCtaScores(cta); }); it("should not modify scores in case of refresh error", async () => { const cta = new CallToActionHashMapFake(); await cta.runRefresh(); cta.setS3Prefix("?"); await cta.runRefresh(); checkCtaScores(cta); }); it("should respect maxRetries parameter", async () => { const cta = new CallToActionHashMapFake(); cta.setS3Prefix("?"); cta.setMaxRetries(100); const spy = sinon.spy(cta, "refreshMaps" as any); await cta.runRefresh(); spy.restore(); expect(spy.callCount).to.equal(101); }); }); describe("getScores", () => { const testCases = { asset_context: { assetId: "a1", ssp: "s1", assetUnit: "au1", }, campaigns: [ { id: "c1", category_iab: [], metadata: {}, metadataTmp: { format: "INT" }, }, { id: "c2", category_iab: [], metadata: {}, metadataTmp: { format: "INT" }, }, { id: "c3", category_iab: [], metadata: {}, metadataTmp: { format: "INT" }, }, { id: "c4", category_iab: [], metadata: {}, metadataTmp: { format: "MPU" }, }, ], campaignCreatives: [ { campaignId: "c1", creativeId: 0, category_iab: [], }, { campaignId: "c2", creativeId: 0, category_iab: [], }, { campaignId: "c3", creativeId: 0, category_iab: [], }, { campaignId: "c4", creativeId: 0, category_iab: [], }, ], }; it("should not fallback", async () => { const cta = new CallToActionHashMapFake(); await cta.runRefresh(); const score = cta.getScore({ format: "INT", ssp: "s1", campaignId: "c1", assetId: "a1", assetUnit: "au1", }); expect(score).to.deep.equal({ score: 0.7, statistics: { bucketsMap: [ { inputUpper: 0.1, outputLower: 0, outputUpper: 0.76, }, { inputUpper: 0.785, outputLower: 0.76, outputUpper: 0.91, }, { inputUpper: 1, outputLower: 0.91, outputUpper: 1, }, ], }, }); }); it("should fallback once", async () => { const cta = new CallToActionHashMapFake(); await cta.runRefresh(); const score = cta.getScore({ format: "INT", ssp: "s1", campaignId: "c2", assetId: "a1", }); expect(score).to.deep.equal({ score: 0.6, statistics: { bucketsMap: [ { inputUpper: 0.1, outputLower: 0, outputUpper: 0.76, }, { inputUpper: 0.785, outputLower: 0.76, outputUpper: 0.93, }, { inputUpper: 1, outputLower: 0.93, outputUpper: 1, }, ], }, }); }); it("should fallback twice", async () => { const cta = new CallToActionHashMapFake(); await cta.runRefresh(); const score = cta.getScore({ format: "INT", ssp: "s1", campaignId: "c2", assetId: "?", assetUnit: "?", }); expect(score).to.deep.equal({ score: 0.5, statistics: { bucketsMap: [ { inputUpper: 0.1, outputLower: 0, outputUpper: 0.76, }, { inputUpper: 0.785, outputLower: 0.76, outputUpper: 0.93, }, { inputUpper: 1, outputLower: 0.93, outputUpper: 1, }, ], }, }); }); it("should fallback three times", async () => { const cta = new CallToActionHashMapFake(); await cta.runRefresh(); const score = cta.getScore({ format: "MPU", ssp: "s1", campaignId: "?", assetId: "?", assetUnit: "?", }); expect(score).to.deep.equal({ score: 0.4, statistics: { bucketsMap: [ { inputUpper: 0.1, outputLower: 0, outputUpper: 0.76, }, { inputUpper: 0.785, outputLower: 0.76, outputUpper: 0.95, }, { inputUpper: 1, outputLower: 0.95, outputUpper: 1, }, ], }, }); }); it("std should fallback it is NaN", async () => { const cta = new CallToActionHashMapFake(); await cta.runRefresh(); const score = cta.getScore({ format: "INT", ssp: "s5", campaignId: "c5", assetId: "a5", assetUnit: "au5", }); expect(score).to.deep.equal({ score: 0.3, statistics: { bucketsMap: [ { inputUpper: 0.1, outputLower: 0, outputUpper: 0.76, }, { inputUpper: 0.785, outputLower: 0.76, outputUpper: 0.92, }, { inputUpper: 1, outputLower: 0.92, outputUpper: 1, }, ], }, }); }); it("should fallback three times and return no score", async () => { const cta = new CallToActionHashMapFake(); await cta.runRefresh(); const score = cta.getScore({ format: "?", ssp: "?", campaignId: "?", assetId: "?", assetUnit: "?", }); expect(score).to.deep.equal({ score: undefined, statistics: { bucketsMap: [], }, }); }); it("should add score and statistics for every request campaign", async () => { const cta = new CallToActionHashMapFake(); await cta.runRefresh(); const creatives: CampaignCreative[] = testCases.campaignCreatives.map((cc) => { const cm = testCases.campaigns.find((c) => c.id === cc.campaignId)!; return { campaign: new FakeCampaign(cm.id), creativeId: cc.creativeId, metadata: Object.assign({}, cm.metadata), metadataTmp: Object.assign({}, cm.metadataTmp), }; }); cta.addScores(creatives, testCases.asset_context); const outputMetadata: any = {}; const outputMetadataTmp: any = {}; creatives.forEach((c) => { outputMetadata[c.campaign.id] = c.metadata; outputMetadataTmp[c.campaign.id] = c.metadataTmp; }); expect(outputMetadata).to.deep.equal({ c1: { cta_score: 0.7 }, c2: { cta_score: 0.6 }, c3: { cta_score: 0.3 }, c4: { cta_score: 0.4 }, }); expect(outputMetadataTmp).to.deep.equal({ c1: { ctaMetadata: { bucketsMap: [ { inputUpper: 0.1, outputLower: 0, outputUpper: 0.76, }, { inputUpper: 0.785, outputLower: 0.76, outputUpper: 0.91, }, { inputUpper: 1, outputLower: 0.91, outputUpper: 1, }, ], }, format: "INT", }, c2: { ctaMetadata: { bucketsMap: [ { inputUpper: 0.1, outputLower: 0, outputUpper: 0.76, }, { inputUpper: 0.785, outputLower: 0.76, outputUpper: 0.93, }, { inputUpper: 1, outputLower: 0.93, outputUpper: 1, }, ], }, format: "INT", }, c3: { ctaMetadata: { bucketsMap: [ { inputUpper: 0.1, outputLower: 0, outputUpper: 0.76, }, { inputUpper: 0.785, outputLower: 0.76, outputUpper: 0.94, }, { inputUpper: 1, outputLower: 0.94, outputUpper: 1, }, ], }, format: "INT", }, c4: { ctaMetadata: { bucketsMap: [ { inputUpper: 0.1, outputLower: 0, outputUpper: 0.76, }, { inputUpper: 0.785, outputLower: 0.76, outputUpper: 0.95, }, { inputUpper: 1, outputLower: 0.95, outputUpper: 1, }, ], }, format: "MPU", }, }); }); }); });