import { expect } from "chai"; import sinon = require("sinon"); import { FakeRequestBuilder } from "../fakeRequestBuilder"; import { StepBuilder } from "../../../src/steps/stepBuilder"; import YieldStep from "../../../src/steps/yield/yield.step"; import { FakeCampaign } from "../fake/Campaign"; import { delivery } from "../../../src/protos/protos"; import YieldService from "../../../src/services/yield.service"; describe("YieldStep", function () { let yieldStep: YieldStep; let yieldService: YieldService let campaignsClientMock: delivery.Campaigns; let msYieldResponse: delivery.RecommenderResponse; beforeEach(() => { campaignsClientMock = { getRecommender: sinon.fake(async (msg: any) => msYieldResponse), } as any; yieldService = new YieldService(campaignsClientMock); yieldStep = new YieldStep(campaignsClientMock, yieldService); }); afterEach(() => { sinon.restore(); yieldService.clearResponseCache(); }); it("should build cache key as adunit, country and sorted campaign ids", async function () { const cacheKey = yieldService.buildCacheKey("11", "PL", [ new FakeCampaign("55"), new FakeCampaign("22"), new FakeCampaign("33"), ]); expect(cacheKey).to.equal("11_PL_22_33_55"); }); it("should return campaigns from cached response if found", async function () { const req = new FakeRequestBuilder() .withCampaigns( new FakeCampaign("12", { bid_price: 1 }), new FakeCampaign("13", { bid_price: 1 }) ) .withContext({ ssp: "ogury", country: "PL" }) .build(); msYieldResponse = delivery.RecommenderResponse.create({ id: "1", difference: 1, bound_lower: 2, bound_upper: 3, app_ecpm: 4, app_eecpm: 5, campaigns: [ { id: "12", selected: true, metadata: { vast_hash: "something" } }, { id: "13", selected: false, metadata: { vast_hash: "something" } }, ], }); const cacheKey = yieldService.buildCacheKey(req.app!.ad_unit_id!, "PL", req.campaigns); yieldService.setResponseCache(cacheKey, msYieldResponse, 60000); const builtStep = new StepBuilder(yieldStep).build(); const res = await builtStep.execute(req); expect(res.passedCampaigns.length).to.equal(1); expect(res.passedCampaigns[0]).to.deep.equal({ id: "12", category_iab: [], metadata: { bid_price: 1, vast_hash: "something", }, metadataTmp: {}, stepsPassedStatus: { pass_yield: "true", }, }); await new Promise((resolve) => setImmediate(resolve)); const getRecommenderMock: sinon.SinonSpy = campaignsClientMock.getRecommender as sinon.SinonSpy; expect(getRecommenderMock.callCount).to.equal(0); }); it("should call ms-yield in case of cache miss", async function () { const req = new FakeRequestBuilder() .withCampaigns( new FakeCampaign("12", { bid_price: 1 }), new FakeCampaign("13", { bid_price: 1 }) ) .withContext({ ssp: "ogury", country: "PL" }) .build(); msYieldResponse = delivery.RecommenderResponse.create({ id: "1", difference: 1, bound_lower: 2, bound_upper: 3, app_ecpm: 4, app_eecpm: 5, campaigns: [ { id: "12", selected: false, metadata: { vast_hash: "something" } }, { id: "13", selected: true, metadata: { vast_hash: "something" } }, ], }); const cacheKey = yieldService.buildCacheKey(req.app!.ad_unit_id!, "PL", req.campaigns); const builtStep = new StepBuilder(yieldStep).build(); const res = await builtStep.execute(req); expect(res.passedCampaigns.length).to.equal(1); expect(res.passedCampaigns[0]).to.deep.equal({ id: "13", category_iab: [], metadata: { bid_price: 1, vast_hash: "something", }, metadataTmp: {}, stepsPassedStatus: { pass_yield: "true", }, }); await new Promise((resolve) => setImmediate(resolve)); const getRecommenderMock: sinon.SinonSpy = campaignsClientMock.getRecommender as sinon.SinonSpy; expect(getRecommenderMock.callCount).to.equal(1); expect(getRecommenderMock.threw()).to.equal(false); const msYieldReq = getRecommenderMock.args[0][0]; expect(msYieldReq.id).to.equal(req.id); expect(msYieldReq.campaigns[0].id).to.equal("12"); expect(msYieldReq.campaigns[1].id).to.equal("13"); expect(yieldService.getResponseCache(cacheKey)).to.deep.equal(msYieldResponse); }); it("should work if ms-yield returns an error", async function () { campaignsClientMock.getRecommender = sinon.fake(async (msg: any) => { throw new Error("argh!"); }); const req = new FakeRequestBuilder() .withCampaigns( new FakeCampaign("12", { bid_price: 1 }), new FakeCampaign("13", { bid_price: 1 }) ) .withContext({ ssp: "ogury", country: "PL" }) .build(); msYieldResponse = delivery.RecommenderResponse.create({ id: "1", difference: 1, bound_lower: 2, bound_upper: 3, app_ecpm: 4, app_eecpm: 5, campaigns: [ { id: "12", selected: false, metadata: { vast_hash: "something" } }, { id: "13", selected: true, metadata: { vast_hash: "something" } }, ], }); const cacheKey = yieldService.buildCacheKey(req.app!.ad_unit_id!, "PL", req.campaigns); const builtStep = new StepBuilder(yieldStep).build(); const res = await builtStep.execute(req); expect(res.passedCampaigns.length).to.equal(0); await new Promise((resolve) => setImmediate(resolve)); const getRecommenderMock: sinon.SinonSpy = campaignsClientMock.getRecommender as sinon.SinonSpy; expect(getRecommenderMock.callCount).to.equal(1); expect(getRecommenderMock.threw()).to.equal(false); const msYieldReq = getRecommenderMock.args[0][0]; expect(msYieldReq.id).to.equal(req.id); expect(msYieldReq.campaigns[0].id).to.equal("12"); expect(msYieldReq.campaigns[1].id).to.equal("13"); expect(yieldService.getResponseCache(cacheKey)).to.deep.equal(null); }); it("should convert numerical app API key to string", async function () { const req = new FakeRequestBuilder() .withCampaigns( new FakeCampaign("12", { bid_price: 1 }), new FakeCampaign("13", { bid_price: 1 }) ) .withContext({ ssp: "ogury", country: "PL" }) .build(); req.app!.app_api_key = 4234; msYieldResponse = delivery.RecommenderResponse.create({ id: "1", difference: 1, bound_lower: 2, bound_upper: 3, app_ecpm: 4, app_eecpm: 5, campaigns: [ { id: "12", selected: false, metadata: { vast_hash: "something" } }, { id: "13", selected: true, metadata: { vast_hash: "something" } }, ], }); const cacheKey = yieldService.buildCacheKey(req.app!.ad_unit_id!, "PL", req.campaigns); const builtStep = new StepBuilder(yieldStep).build(); const res = await builtStep.execute(req); expect(res.passedCampaigns.length).to.equal(1); expect(res.passedCampaigns[0]).to.deep.equal({ id: "13", category_iab: [], metadata: { bid_price: 1, vast_hash: "something", }, metadataTmp: {}, stepsPassedStatus: { pass_yield: "true", }, }); await new Promise((resolve) => setImmediate(resolve)); const getRecommenderMock: sinon.SinonSpy = campaignsClientMock.getRecommender as sinon.SinonSpy; expect(getRecommenderMock.callCount).to.equal(1); expect(getRecommenderMock.threw()).to.equal(false); const msYieldReq = getRecommenderMock.args[0][0]; expect(msYieldReq.id).to.equal(req.id); expect(msYieldReq.app.app_api_key).to.equal("4234"); expect(msYieldReq.campaigns[0].id).to.equal("12"); expect(msYieldReq.campaigns[1].id).to.equal("13"); expect(yieldService.getResponseCache(cacheKey)).to.deep.equal(msYieldResponse); }); });