import { GlobalOverrider } from "test/unit/utils"; import { PersonalityProvider } from "lib/PersonalityProvider/PersonalityProvider.sys.mjs"; describe("Personality Provider", () => { let instance; let RemoteSettingsStub; let RemoteSettingsOnStub; let RemoteSettingsOffStub; let RemoteSettingsGetStub; let sandbox; let globals; let baseURLStub; beforeEach(() => { sandbox = sinon.createSandbox(); globals = new GlobalOverrider(); RemoteSettingsOnStub = sandbox.stub().returns(); RemoteSettingsOffStub = sandbox.stub().returns(); RemoteSettingsGetStub = sandbox.stub().returns([]); RemoteSettingsStub = () => ({ get: RemoteSettingsGetStub, on: RemoteSettingsOnStub, off: RemoteSettingsOffStub, }); sinon.spy(global, "BasePromiseWorker"); sinon.spy(global.BasePromiseWorker.prototype, "post"); baseURLStub = "https://baseattachmentsurl"; global.fetch = async server => ({ ok: true, json: async () => { if (server === "bogus://foo/") { return { capabilities: { attachments: { base_url: baseURLStub } } }; } return {}; }, }); globals.set("RemoteSettings", RemoteSettingsStub); instance = new PersonalityProvider(); instance.interestConfig = { history_item_builder: "history_item_builder", history_required_fields: ["a", "b", "c"], interest_finalizer: "interest_finalizer", item_to_rank_builder: "item_to_rank_builder", item_ranker: "item_ranker", interest_combiner: "interest_combiner", }; }); afterEach(() => { sinon.restore(); sandbox.restore(); globals.restore(); }); describe("#personalityProviderWorker", () => { it("should create a new promise worker on first call", async () => { const { personalityProviderWorker } = instance; assert.calledOnce(global.BasePromiseWorker); assert.isDefined(personalityProviderWorker); }); it("should cache _personalityProviderWorker on first call", async () => { instance._personalityProviderWorker = null; const { personalityProviderWorker } = instance; assert.isDefined(instance._personalityProviderWorker); assert.isDefined(personalityProviderWorker); }); it("should use old promise worker on second call", async () => { let { personalityProviderWorker } = instance; personalityProviderWorker = instance.personalityProviderWorker; assert.calledOnce(global.BasePromiseWorker); assert.isDefined(personalityProviderWorker); }); }); describe("#_getBaseAttachmentsURL", () => { it("should return a fresh value", async () => { await instance._getBaseAttachmentsURL(); assert.equal(instance._baseAttachmentsURL, baseURLStub); }); it("should return a cached value", async () => { const cachedURL = "cached"; instance._baseAttachmentsURL = cachedURL; await instance._getBaseAttachmentsURL(); assert.equal(instance._baseAttachmentsURL, cachedURL); }); }); describe("#setup", () => { it("should setup two sync attachments", () => { sinon.spy(instance, "setupSyncAttachment"); instance.setup(); assert.calledTwice(instance.setupSyncAttachment); }); }); describe("#teardown", () => { it("should teardown two sync attachments", () => { sinon.spy(instance, "teardownSyncAttachment"); instance.teardown(); assert.calledTwice(instance.teardownSyncAttachment); }); it("should terminate worker", () => { const terminateStub = sandbox.stub().returns(); instance._personalityProviderWorker = { terminate: terminateStub, }; instance.teardown(); assert.calledOnce(terminateStub); }); }); describe("#setupSyncAttachment", () => { it("should call remote settings on twice for setupSyncAttachment", () => { assert.calledTwice(RemoteSettingsOnStub); }); }); describe("#teardownSyncAttachment", () => { it("should call remote settings off for teardownSyncAttachment", () => { instance.teardownSyncAttachment(); assert.calledOnce(RemoteSettingsOffStub); }); }); describe("#onSync", () => { it("should call worker onSync", () => { instance.onSync(); assert.calledWith(global.BasePromiseWorker.prototype.post, "onSync"); }); }); describe("#getAttachment", () => { it("should call worker onSync", () => { instance.getAttachment(); assert.calledWith( global.BasePromiseWorker.prototype.post, "getAttachment" ); }); }); describe("#getRecipe", () => { it("should call worker getRecipe and remote settings get", async () => { RemoteSettingsGetStub = sandbox.stub().returns([ { key: 1, }, ]); sinon.spy(instance, "getAttachment"); RemoteSettingsStub = () => ({ get: RemoteSettingsGetStub, on: RemoteSettingsOnStub, off: RemoteSettingsOffStub, }); globals.set("RemoteSettings", RemoteSettingsStub); const result = await instance.getRecipe(); assert.calledOnce(RemoteSettingsGetStub); assert.calledOnce(instance.getAttachment); assert.equal(result.recordKey, 1); }); }); describe("#fetchHistory", () => { it("should return a history object for fetchHistory", async () => { const history = await instance.fetchHistory(["requiredColumn"], 1, 1); assert.equal( history.sql, `SELECT url, title, visit_count, frecency, last_visit_date, description\n FROM moz_places\n WHERE last_visit_date >= 1000000\n AND last_visit_date < 1000000 AND IFNULL(requiredColumn, '') <> '' LIMIT 30000` ); assert.equal(history.options.columns.length, 1); assert.equal(Object.keys(history.options.params).length, 0); }); }); describe("#getHistory", () => { it("should return an empty array", async () => { instance.interestConfig = { history_required_fields: [], }; const result = await instance.getHistory(); assert.equal(result.length, 0); }); it("should call fetchHistory", async () => { sinon.spy(instance, "fetchHistory"); await instance.getHistory(); }); }); describe("#setBaseAttachmentsURL", () => { it("should call worker setBaseAttachmentsURL", async () => { await instance.setBaseAttachmentsURL(); assert.calledWith( global.BasePromiseWorker.prototype.post, "setBaseAttachmentsURL" ); }); }); describe("#setInterestConfig", () => { it("should call worker setInterestConfig", async () => { await instance.setInterestConfig(); assert.calledWith( global.BasePromiseWorker.prototype.post, "setInterestConfig" ); }); }); describe("#setInterestVector", () => { it("should call worker setInterestVector", async () => { await instance.setInterestVector(); assert.calledWith( global.BasePromiseWorker.prototype.post, "setInterestVector" ); }); }); describe("#fetchModels", () => { it("should call worker fetchModels and remote settings get", async () => { await instance.fetchModels(); assert.calledOnce(RemoteSettingsGetStub); assert.calledWith(global.BasePromiseWorker.prototype.post, "fetchModels"); }); }); describe("#generateTaggers", () => { it("should call worker generateTaggers", async () => { await instance.generateTaggers(); assert.calledWith( global.BasePromiseWorker.prototype.post, "generateTaggers" ); }); }); describe("#generateRecipeExecutor", () => { it("should call worker generateRecipeExecutor", async () => { await instance.generateRecipeExecutor(); assert.calledWith( global.BasePromiseWorker.prototype.post, "generateRecipeExecutor" ); }); }); describe("#createInterestVector", () => { it("should call worker createInterestVector", async () => { await instance.createInterestVector(); assert.calledWith( global.BasePromiseWorker.prototype.post, "createInterestVector" ); }); }); describe("#init", () => { it("should return early if setInterestConfig fails", async () => { sandbox.stub(instance, "setBaseAttachmentsURL").returns(); sandbox.stub(instance, "setInterestConfig").returns(); instance.interestConfig = null; const callback = globals.sandbox.stub(); await instance.init(callback); assert.notCalled(callback); }); it("should return early if fetchModels fails", async () => { sandbox.stub(instance, "setBaseAttachmentsURL").returns(); sandbox.stub(instance, "setInterestConfig").returns(); sandbox.stub(instance, "fetchModels").resolves({ ok: false, }); const callback = globals.sandbox.stub(); await instance.init(callback); assert.notCalled(callback); }); it("should return early if createInterestVector fails", async () => { sandbox.stub(instance, "setBaseAttachmentsURL").returns(); sandbox.stub(instance, "setInterestConfig").returns(); sandbox.stub(instance, "fetchModels").resolves({ ok: true, }); sandbox.stub(instance, "generateRecipeExecutor").resolves({ ok: true, }); sandbox.stub(instance, "createInterestVector").resolves({ ok: false, }); const callback = globals.sandbox.stub(); await instance.init(callback); assert.notCalled(callback); }); it("should call callback on successful init", async () => { sandbox.stub(instance, "setBaseAttachmentsURL").returns(); sandbox.stub(instance, "setInterestConfig").returns(); sandbox.stub(instance, "fetchModels").resolves({ ok: true, }); sandbox.stub(instance, "generateRecipeExecutor").resolves({ ok: true, }); sandbox.stub(instance, "createInterestVector").resolves({ ok: true, }); sandbox.stub(instance, "setInterestVector").resolves(); const callback = globals.sandbox.stub(); await instance.init(callback); assert.calledOnce(callback); assert.isTrue(instance.initialized); }); it("should do generic init stuff when calling init with no cache", async () => { sandbox.stub(instance, "setBaseAttachmentsURL").returns(); sandbox.stub(instance, "setInterestConfig").returns(); sandbox.stub(instance, "fetchModels").resolves({ ok: true, }); sandbox.stub(instance, "generateRecipeExecutor").resolves({ ok: true, }); sandbox.stub(instance, "createInterestVector").resolves({ ok: true, interestVector: "interestVector", }); sandbox.stub(instance, "setInterestVector").resolves(); await instance.init(); assert.calledOnce(instance.setBaseAttachmentsURL); assert.calledOnce(instance.setInterestConfig); assert.calledOnce(instance.fetchModels); assert.calledOnce(instance.generateRecipeExecutor); assert.calledOnce(instance.createInterestVector); assert.calledOnce(instance.setInterestVector); }); }); describe("#calculateItemRelevanceScore", () => { it("should return score for uninitialized provider", async () => { instance.initialized = false; assert.equal( await instance.calculateItemRelevanceScore({ item_score: 2 }), 2 ); }); it("should return score for initialized provider", async () => { instance.initialized = true; instance._personalityProviderWorker = { post: (postName, [item]) => ({ rankingVector: { score: item.item_score }, }), }; assert.equal( await instance.calculateItemRelevanceScore({ item_score: 2 }), 2 ); }); it("should post calculateItemRelevanceScore to PersonalityProviderWorker", async () => { instance.initialized = true; await instance.calculateItemRelevanceScore({ item_score: 2 }); assert.calledWith( global.BasePromiseWorker.prototype.post, "calculateItemRelevanceScore" ); }); }); describe("#getScores", () => { it("should return correct data for getScores", () => { const scores = instance.getScores(); assert.isDefined(scores.interestConfig); }); }); });