diff --git a/modules/gamAdServerSubmodule.js b/modules/gamAdServerSubmodule.js new file mode 100644 index 00000000000..e2d01e6f21c --- /dev/null +++ b/modules/gamAdServerSubmodule.js @@ -0,0 +1,22 @@ +import { GAM_VENDOR } from './videoModule/constants/vendorCodes.js'; +import { adServerDirectory } from './videoModule/vendorDirectory.js'; + +function GamAdServerProvider(dfpModule_) { + const dfp = dfpModule_; + + function getAdTagUrl(adUnit, baseAdTag) { + return dfp.buildVideoUrl({ adUnit: adUnit, url: baseAdTag }); + } + + return { + getAdTagUrl + } +} +function gamSubmoduleFactory() { + const dfp = $$PREBID_GLOBAL$$.adServers.dfp; + const gamProvider = GamAdServerProvider(dfp); + return gamProvider; +} + +gamSubmoduleFactory.vendorCode = GAM_VENDOR; +adServerDirectory[GAM_VENDOR] = gamSubmoduleFactory; diff --git a/modules/jwplayerVideoProvider.js b/modules/jwplayerVideoProvider.js index 3bd47060107..d5551fd7f94 100644 --- a/modules/jwplayerVideoProvider.js +++ b/modules/jwplayerVideoProvider.js @@ -9,7 +9,7 @@ import { } from './videoModule/constants/events.js'; import stateFactory from './videoModule/shared/state.js'; import { JWPLAYER_VENDOR } from './videoModule/constants/vendorCodes.js'; -import { vendorDirectory } from './videoModule/vendorDirectory.js'; +import { videoVendorDirectory } from './videoModule/vendorDirectory.js'; export function JWPlayerProvider(config, jwplayer_, adState_, timeState_, callbackStorage_, utils) { const jwplayer = jwplayer_; @@ -124,11 +124,11 @@ export function JWPlayerProvider(config, jwplayer_, adState_, timeState_, callba } } - function setAdTagUrl(adTagUrl) { + function setAdTagUrl(adTagUrl, options) { if (!player) { return; } - player.playAd(adTagUrl); + player.playAd(adTagUrl || options.adXml, options); } function onEvents(events, callback) { @@ -632,7 +632,7 @@ const jwplayerSubmoduleFactory = function (config) { } jwplayerSubmoduleFactory.vendorCode = JWPLAYER_VENDOR; -vendorDirectory[JWPLAYER_VENDOR] = jwplayerSubmoduleFactory; +videoVendorDirectory[JWPLAYER_VENDOR] = jwplayerSubmoduleFactory; export default jwplayerSubmoduleFactory; // HELPERS diff --git a/modules/videoModule/adServer.js b/modules/videoModule/adServer.js new file mode 100644 index 00000000000..3c6ddf9c86c --- /dev/null +++ b/modules/videoModule/adServer.js @@ -0,0 +1,28 @@ +import { adServerDirectory } from './vendorDirectory.js'; +import { ParentModule, SubmoduleBuilder } from './shared/parentModule.js'; + +export function AdServerCore(parentModule_) { + const parentModule = parentModule_; + + function registerAdServer(config) { + const vendorCode = config.vendorCode; + parentModule.registerSubmodule(vendorCode, vendorCode, config); + } + + function getAdTagUrl(vendorCode, adUnit, baseAdTagUrl) { + const submodule = parentModule.getSubmodule(vendorCode); + return submodule && submodule.getAdTagUrl(adUnit, baseAdTagUrl); + } + + return { + registerAdServer, + getAdTagUrl + } +} + +export function coreAdServerFactory() { + const adServerSubmoduleBuilder = SubmoduleBuilder(adServerDirectory); + const parentModule = ParentModule(adServerSubmoduleBuilder); + const adServerCore = AdServerCore(parentModule); + return adServerCore; +} diff --git a/modules/videoModule/constants/vendorCodes.js b/modules/videoModule/constants/vendorCodes.js index 090b383fbf8..370c151b997 100644 --- a/modules/videoModule/constants/vendorCodes.js +++ b/modules/videoModule/constants/vendorCodes.js @@ -1,2 +1,6 @@ +// Video Vendors export const JWPLAYER_VENDOR = 1; export const VIDEO_JS_VENDOR = 2; + +// Ad Server Vendors +export const GAM_VENDOR = 'gam'; diff --git a/modules/videoModule/coreVideo.js b/modules/videoModule/coreVideo.js index 36b0a90f4ff..af3eaa8a80b 100644 --- a/modules/videoModule/coreVideo.js +++ b/modules/videoModule/coreVideo.js @@ -1,41 +1,30 @@ -import { vendorDirectory } from './vendorDirectory.js'; +import { videoVendorDirectory } from './vendorDirectory.js'; +import { ParentModule, SubmoduleBuilder } from './shared/parentModule.js'; -export function VideoCore(submoduleBuilder_) { - const submodules = {}; - const submoduleBuilder = submoduleBuilder_; +export function VideoCore(parentModule_) { + const parentModule = parentModule_; function registerProvider(providerConfig) { - const divId = providerConfig.divId; - if (submodules[divId]) { - return; - } - - let submodule; - try { - submodule = submoduleBuilder.build(providerConfig); - } catch (e) { - throw e; - } - submodules[divId] = submodule; + parentModule.registerSubmodule(providerConfig.divId, providerConfig.vendorCode, providerConfig); } function getOrtbParams(divId) { - const submodule = submodules[divId]; + const submodule = parentModule.getSubmodule(divId); return submodule && submodule.getOrtbParams(); } - function setAdTagUrl(adTagUrl, divId) { - const submodule = submodules[divId]; - return submodule && submodule.setAdTagUrl(adTagUrl); + function setAdTagUrl(adTagUrl, divId, options) { + const submodule = parentModule.getSubmodule(divId); + return submodule && submodule.setAdTagUrl(adTagUrl, options); } function onEvents(events, callback, divId) { - const submodule = submodules[divId]; + const submodule = parentModule.getSubmodule(divId); return submodule && submodule.onEvents(events, callback); } function offEvents(events, callback, divId) { - const submodule = submodules[divId]; + const submodule = parentModule.getSubmodule(divId); return submodule && submodule.offEvents(events, callback); } @@ -49,25 +38,7 @@ export function VideoCore(submoduleBuilder_) { } export function videoCoreFactory() { - const submoduleBuilder = VideoSubmoduleBuilder(vendorDirectory); - return VideoCore(submoduleBuilder); -} - -export function VideoSubmoduleBuilder(vendorDirectory_) { - const vendorDirectory = vendorDirectory_; - - function build(providerConfig) { - const submoduleFactory = vendorDirectory[providerConfig.vendorCode]; - if (!submoduleFactory) { - throw new Error('Unrecognized vendor code'); - } - - const submodule = submoduleFactory(providerConfig); - submodule && submodule.init && submodule.init(); - return submodule; - } - - return { - build - }; + const videoSubmoduleBuilder = SubmoduleBuilder(videoVendorDirectory); + const parentModule = ParentModule(videoSubmoduleBuilder); + return VideoCore(parentModule); } diff --git a/modules/videoModule/index.js b/modules/videoModule/index.js index 774704a4c32..2f618bc4733 100644 --- a/modules/videoModule/index.js +++ b/modules/videoModule/index.js @@ -1,18 +1,20 @@ -import {config} from '../../src/config.js'; +import { config } from '../../src/config.js'; import events from '../../src/events.js'; import { allVideoEvents } from './constants/events.js'; import CONSTANTS from '../../src/constants.json'; import { videoCoreFactory } from './coreVideo.js'; +import { coreAdServerFactory } from './adServer.js'; events.addEvents(allVideoEvents); -export function PbVideo(videoCore_, getConfig_, pbGlobal_, pbEvents_, videoEvents_) { +export function PbVideo(videoCore_, getConfig_, pbGlobal_, pbEvents_, videoEvents_, adServerCore_) { const videoCore = videoCore_; const getConfig = getConfig_; const pbGlobal = pbGlobal_; const requestBids = pbGlobal.requestBids; const pbEvents = pbEvents_; const videoEvents = videoEvents_; + const adServerCore = adServerCore_; function init() { getConfig('video', ({ video }) => { @@ -23,18 +25,27 @@ export function PbVideo(videoCore_, getConfig_, pbGlobal_, pbEvents_, videoEvent pbEvents.emit(type, payload); }, provider.divId); } catch (e) {} + + const adServerConfig = provider.adServer; + if (adServerConfig) { + adServerCore.registerAdServer(adServerConfig.vendorCode, adServerConfig.params); + } }); }); requestBids.before(enrichAdUnits, 40); pbEvents.on(CONSTANTS.EVENTS.AUCTION_END, function(auctionResult) { - // TODO: requires AdServer Module. - // get ad tag from adServer - auctionResult.winningBids - // coreVideo.setAdTagUrl(adTag, divId); + auctionResult.adUnits.forEach(adUnit => { + if (adUnit.video) { + renderWinningBid(adUnit); + } + }); }); } + return { init }; + function enrichAdUnits(nextFn, bidRequest) { const adUnits = bidRequest.adUnits || pbGlobal.adUnits || []; adUnits.forEach(adUnit => { @@ -44,14 +55,38 @@ export function PbVideo(videoCore_, getConfig_, pbGlobal_, pbEvents_, videoEvent return nextFn.call(this, bidRequest); } - return { init }; + function renderWinningBid(adUnit) { + const videoConfig = adUnit.video; + const divId = videoConfig.divId; + const adServerConfig = videoConfig.adServer; + let adTagUrl; + if (adServerConfig) { + adTagUrl = adServerCore.getAdTagUrl(adServerConfig.vendorCode, adUnit, adServerConfig.baseAdTagUrl); + } + + const adUnitCode = adUnit.code; + const options = { adUnitCode }; + if (adTagUrl) { + videoCore.setAdTagUrl(adTagUrl, divId, options); + return; + } + + const highestCpmBids = pbGlobal.getHighestCpmBids(adUnit.code); + const highestBid = highestCpmBids && highestCpmBids.shift(); + if (!highestBid) { + return; + } + + adTagUrl = highestBid.vastUrl; + options.adXml = highestBid.vastXml; + videoCore.setAdTagUrl(adTagUrl, divId, options); + } } -function pbVideoFactory() { +export function pbVideoFactory() { const videoCore = videoCoreFactory(); - const pbVideo = PbVideo(videoCore, config.getConfig, $$PREBID_GLOBAL$$, events, allVideoEvents); + const adServerCore = coreAdServerFactory(); + const pbVideo = PbVideo(videoCore, config.getConfig, $$PREBID_GLOBAL$$, events, allVideoEvents, adServerCore); pbVideo.init(); return pbVideo; } - -pbVideoFactory(); diff --git a/modules/videoModule/shared/parentModule.js b/modules/videoModule/shared/parentModule.js new file mode 100644 index 00000000000..e235475a328 --- /dev/null +++ b/modules/videoModule/shared/parentModule.js @@ -0,0 +1,55 @@ + +/* +Used by any module to manage the relationship with its submodules. + */ +export function ParentModule(submoduleBuilder_) { + const submoduleBuilder = submoduleBuilder_; + const submodules = {}; + + /* + id: identifies the submodule instance + vendorCode: identifies the submodule type that must be built + config: additional information necessary to instantiate the instance + */ + function registerSubmodule(id, vendorCode, config) { + if (submodules[id]) { + return; + } + + let submodule; + try { + submodule = submoduleBuilder.build(vendorCode, config); + } catch (e) { + throw e; + } + submodules[id] = submodule; + } + + function getSubmodule(id) { + return submodules[id]; + } + + return { + registerSubmodule, + getSubmodule + } +} + +export function SubmoduleBuilder(submoduleDirectory_) { + const submoduleDirectory = submoduleDirectory_; + + function build(vendorCode, config) { + const submoduleFactory = submoduleDirectory[vendorCode]; + if (!submoduleFactory) { + throw new Error('Unrecognized submodule vendor code: ' + vendorCode); + } + + const submodule = submoduleFactory(config); + submodule && submodule.init && submodule.init(); + return submodule; + } + + return { + build + }; +} diff --git a/modules/videoModule/vendorDirectory.js b/modules/videoModule/vendorDirectory.js index 9d6b831b339..79f311b5049 100644 --- a/modules/videoModule/vendorDirectory.js +++ b/modules/videoModule/vendorDirectory.js @@ -1 +1,2 @@ -export const vendorDirectory = {}; +export const videoVendorDirectory = {}; +export const adServerDirectory = {}; diff --git a/test/spec/modules/videoModule/adServer_spec.js b/test/spec/modules/videoModule/adServer_spec.js new file mode 100644 index 00000000000..11132d3fb5d --- /dev/null +++ b/test/spec/modules/videoModule/adServer_spec.js @@ -0,0 +1,28 @@ +import { expect } from 'chai'; +import { AdServerCore } from 'modules/videoModule/adServer.js'; + +describe('Ad Server Core', function () { + const parentModuleMock = { + registerSubmodule: sinon.spy(), + getSubmodule: sinon.spy() + }; + const testVendorCode = 'test'; + const adServerCore = AdServerCore(parentModuleMock); + adServerCore.registerAdServer({ vendorCode: testVendorCode }); + + describe('Register Ad Server', function () { + it('should use the vendor code as an id as well as a vendor code', function () { + expect(parentModuleMock.registerSubmodule.calledOnce).to.be.true; + expect(parentModuleMock.registerSubmodule.args[0][0]).to.be.equal(testVendorCode); + expect(parentModuleMock.registerSubmodule.args[0][1]).to.be.equal(testVendorCode); + }); + }); + + describe('Get Ad Tag Url', function () { + it('should request the right submodule', function () { + adServerCore.getAdTagUrl(testVendorCode); + expect(parentModuleMock.getSubmodule.calledOnce).to.be.true; + expect(parentModuleMock.getSubmodule.args[0][0]).to.be.equal(testVendorCode); + }); + }); +}); diff --git a/test/spec/modules/videoModule/coreVideo_spec.js b/test/spec/modules/videoModule/coreVideo_spec.js index b5f621fbab1..40e424caf99 100644 --- a/test/spec/modules/videoModule/coreVideo_spec.js +++ b/test/spec/modules/videoModule/coreVideo_spec.js @@ -1,25 +1,5 @@ import { expect } from 'chai'; -import { - VideoSubmoduleBuilder, - VideoCore -} from 'modules/videoModule/coreVideo.js'; - -describe('Video Submodule Builder', function () { - const playerSpecificSubmoduleFactory = sinon.spy(); - const vendorDirectory = { - 1: playerSpecificSubmoduleFactory - }; - const submoduleBuilder = VideoSubmoduleBuilder(vendorDirectory); - - it('should call submodule factory when vendor code is supported', function () { - submoduleBuilder.build({ vendorCode: 1 }); - expect(playerSpecificSubmoduleFactory.calledOnce).to.be.true; - }); - - it('should throw when vendor code is not recognized', function () { - expect(() => submoduleBuilder.build({ vendorCode: 2 })).to.throw('Unrecognized vendor code'); - }); -}); +import { VideoCore } from 'modules/videoModule/coreVideo.js'; describe('Video Core', function () { const mockSubmodule = { @@ -36,38 +16,41 @@ describe('Video Core', function () { offEvents: () => {}, }; - const mockSubmoduleBuilder = { - build: (config) => { - if (config.vendorCode === 0) { + const testId = 'test_id'; + const testVendorCode = 0; + const otherId = 'other_id'; + const otherVendorCode = 1; + + const parentModuleMock = { + registerSubmodule: sinon.spy(), + getSubmodule: sinon.spy(id => { + if (id === testId) { return mockSubmodule; - } else { + } else if (id === otherId) { return otherSubmodule; } - } + }) }; - const videoCore = VideoCore(mockSubmoduleBuilder); - const testId = 'test_id'; - const otherId = 'other_id'; + const videoCore = VideoCore(parentModuleMock); videoCore.registerProvider({ - vendorCode: 0, + vendorCode: testVendorCode, divId: testId }); videoCore.registerProvider({ - vendorCode: 1, + vendorCode: otherVendorCode, divId: otherId }); describe('registerProvider', function () { - it('should throw when the builder fails to build', function () { - const flawedVideoCore = VideoCore({ - build: () => { - throw new Error('flawed'); - } - }); - expect(() => flawedVideoCore.registerProvider({ vendorCode: 0 })).to.throw('flawed'); + it('should delegate the registration to the Parent Module', function () { + expect(parentModuleMock.registerSubmodule.calledTwice).to.be.true; + expect(parentModuleMock.registerSubmodule.args[0][0]).to.be.equal(testId); + expect(parentModuleMock.registerSubmodule.args[1][0]).to.be.equal(otherId); + expect(parentModuleMock.registerSubmodule.args[0][1]).to.be.equal(testVendorCode); + expect(parentModuleMock.registerSubmodule.args[1][1]).to.be.equal(otherVendorCode); }); }); diff --git a/test/spec/modules/videoModule/pbVideo_spec.js b/test/spec/modules/videoModule/pbVideo_spec.js index 10d4c5e494e..ea0a54dc2eb 100644 --- a/test/spec/modules/videoModule/pbVideo_spec.js +++ b/test/spec/modules/videoModule/pbVideo_spec.js @@ -8,6 +8,7 @@ let requestBidsMock; let pbGlobalMock; let pbEventsMock; let videoEventsMock; +let adServerMock; function resetTestVars() { ortbParamsMock = { @@ -17,29 +18,36 @@ function resetTestVars() { videoCoreMock = { registerProvider: sinon.spy(), onEvents: sinon.spy(), - getOrtbParams: () => ortbParamsMock + getOrtbParams: () => ortbParamsMock, + setAdTagUrl: sinon.spy() }; getConfigMock = () => {}; requestBidsMock = { before: sinon.spy() }; pbGlobalMock = { - requestBids: requestBidsMock + requestBids: requestBidsMock, + getHighestCpmBids: sinon.spy() }; pbEventsMock = { emit: sinon.spy(), on: sinon.spy() }; videoEventsMock = []; + adServerMock = { + registerAdServer: sinon.spy(), + getAdTagUrl: sinon.spy() + }; } -let pbVideoFactory = (videoCore, getConfig, pbGlobal, pbEvents, videoEvents) => { +let pbVideoFactory = (videoCore, getConfig, pbGlobal, pbEvents, videoEvents, adServer) => { const pbVideo = PbVideo( videoCore || videoCoreMock, getConfig || getConfigMock, pbGlobal || pbGlobalMock, pbEvents || pbEventsMock, - videoEvents || videoEventsMock + videoEvents || videoEventsMock, + adServer || adServerMock ); pbVideo.init(); return pbVideo; @@ -87,6 +95,18 @@ describe('Prebid Video', function () { expect(pbEventsMock.emit.getCall(0).args[1]).to.be.equal(expectedPayload); }); }); + + describe('Ad Server configuration', function() { + const test_vendor_code = 5; + const test_params = { test: 'params' }; + providers[0].adServer = { vendorCode: test_vendor_code, params: test_params }; + + it('should register the ad server provider', function () { + expect(adServerMock.registerAdServer.calledOnce).to.be.true; + expect(adServerMock.registerAdServer.getCall(0).args[0]).to.be.equal(test_vendor_code); + expect(adServerMock.registerAdServer.getCall(0).args[1]).to.be.equal(test_params); + }); + }); }); describe('Ad unit Enrichment', function () { @@ -122,6 +142,75 @@ describe('Prebid Video', function () { }); describe('Ad tag injection', function () { - // TODO: requires adServer to be implemented + let auctionEndCallback; + const pbEvents = { + emit: () => {}, + on: (event, callback) => auctionEndCallback = callback + }; + + const expectedVendorCode = 5; + const expectedAdTag = 'test_tag'; + const expectedAdUnitCode = 'expectedAdUnitcode'; + const expectedDivId = 'expectedDivId'; + const expectedAdUnit = { + code: expectedAdUnitCode, + video: { + divId: expectedDivId, + adServer: { + vendorCode: expectedVendorCode, + baseAdTagUrl: expectedAdTag + } + } + }; + const auctionResults = { adUnits: [ expectedAdUnit, {} ] }; + + it('should request ad tag url from adServer when configured to use adServer', function () { + pbVideoFactory(null, null, null, pbEvents); + + auctionEndCallback(auctionResults); + expect(adServerMock.getAdTagUrl.calledOnce).to.be.true; + expect(adServerMock.getAdTagUrl.getCall(0).args[0]).is.equal(expectedVendorCode); + expect(adServerMock.getAdTagUrl.getCall(0).args[1]).is.equal(expectedAdUnit); + expect(adServerMock.getAdTagUrl.getCall(0).args[2]).is.equal(expectedAdTag); + + expect(videoCoreMock.setAdTagUrl.called).to.be.false; + }); + + it('should load ad tag when ad server return ad tag', function () { + const expectedAdTag = 'resulting ad tag'; + const adServerCore = Object.assign({}, adServerMock, { + getAdTagUrl: () => expectedAdTag + }); + pbVideoFactory(null, null, null, pbEvents, null, adServerCore); + auctionEndCallback(auctionResults); + expect(videoCoreMock.setAdTagUrl.calledOnce).to.be.true; + expect(videoCoreMock.setAdTagUrl.args[0][0]).to.be.equal(expectedAdTag); + expect(videoCoreMock.setAdTagUrl.args[0][1]).to.be.equal(expectedDivId); + expect(videoCoreMock.setAdTagUrl.args[0][2]).to.have.property('adUnitCode', expectedAdUnitCode); + }); + + it('should load ad tag from highest bid when ad server is not configured', function () { + const expectedVastUrl = 'expectedVastUrl'; + const expectedVastXml = 'expectedVastXml'; + const pbGlobal = Object.assign({}, pbGlobalMock, { + getHighestCpmBids: () => [{ + vastUrl: expectedVastUrl, + vastXml: expectedVastXml + }, {}, {}, {}] + }); + const expectedAdUnit = { + code: expectedAdUnitCode, + video: { divId: expectedDivId } + }; + const auctionResults = { adUnits: [ expectedAdUnit, {} ] }; + + pbVideoFactory(null, null, pbGlobal, pbEvents); + auctionEndCallback(auctionResults); + expect(videoCoreMock.setAdTagUrl.calledOnce).to.be.true; + expect(videoCoreMock.setAdTagUrl.args[0][0]).to.be.equal(expectedVastUrl); + expect(videoCoreMock.setAdTagUrl.args[0][1]).to.be.equal(expectedDivId); + expect(videoCoreMock.setAdTagUrl.args[0][2]).to.have.property('adUnitCode', expectedAdUnitCode); + expect(videoCoreMock.setAdTagUrl.args[0][2]).to.have.property('adXml', expectedVastXml); + }); }); }); diff --git a/test/spec/modules/videoModule/shared/parentModule_spec.js b/test/spec/modules/videoModule/shared/parentModule_spec.js new file mode 100644 index 00000000000..c9f7e453689 --- /dev/null +++ b/test/spec/modules/videoModule/shared/parentModule_spec.js @@ -0,0 +1,73 @@ +import { SubmoduleBuilder, ParentModule } from 'modules/videoModule/shared/parentModule.js'; +import { expect } from 'chai'; + +describe('Parent Module', function() { + const idForMock = 0; + const vendorCodeForMock = 'a'; + const unrecognizedId = 999; + const unrecognizedVendorCode = 'zzz'; + const mockSubmodule = { test: 'test' }; + const mockSubmoduleBuilder = { + build: vendorCode => { + if (vendorCode === vendorCodeForMock) { + return mockSubmodule; + } else { + throw new Error('flawed'); + } + } + }; + const parentModule = ParentModule(mockSubmoduleBuilder); + + describe('Register Submodule', function () { + it('should throw when the builder fails to build', function () { + expect(() => parentModule.registerSubmodule(unrecognizedId, unrecognizedVendorCode)).to.throw('flawed'); + }); + }); + + describe('Get Submodule', function () { + it('should return registered submodules', function () { + parentModule.registerSubmodule(idForMock, vendorCodeForMock); + const submodule = parentModule.getSubmodule(idForMock); + expect(submodule).to.be.equal(mockSubmodule); + }); + + it('should return undefined when submodule is not registered', function () { + const submodule = parentModule.getSubmodule(unrecognizedId); + expect(submodule).to.be.undefined; + }); + }) +}); + +describe('Submodule Builder', function () { + const vendorCode1 = 1; + const vendorCode2 = 2; + const submodule1 = {}; + const initSpy = sinon.spy(); + const submodule2 = { init: initSpy }; + const submoduleFactory1 = () => submodule1; + const submoduleFactory2 = () => submodule2; + const submoduleFactory1Spy = sinon.spy(submoduleFactory1); + + const vendorDirectory = {}; + vendorDirectory[vendorCode1] = submoduleFactory1Spy; + vendorDirectory[vendorCode2] = submoduleFactory2; + + const submoduleBuilder = SubmoduleBuilder(vendorDirectory); + + it('should call submodule factory when vendor code is supported', function () { + const submodule = submoduleBuilder.build(vendorCode1); + expect(submoduleFactory1Spy.calledOnce).to.be.true; + expect(submodule).to.be.equal(submodule1); + }); + + it('should instantiate the submodule, when supported', function () { + const submodule = submoduleBuilder.build(vendorCode2); + expect(initSpy.calledOnce).to.be.true; + expect(submodule).to.be.equal(submodule2); + }); + + it('should throw when vendor code is not recognized', function () { + const unrecognizedVendorCode = 999; + expect(() => submoduleBuilder.build(unrecognizedVendorCode)).to.throw('Unrecognized submodule vendor code: ' + unrecognizedVendorCode); + }); +});