Files
hls.js/tests/unit/controller/cmcd-controller.ts
Casey OcchialiniandGitHub 805e55535e Bugfix: use part details to construct CMCD data when available (#6395)
* fix: use part details to construct CMCD data when available
* test: add unit test for part fragment CMCD
Fixes: #6388
2024-05-03 09:52:48 -07:00

175 lines
5.0 KiB
TypeScript

import CMCDController from '../../../src/controller/cmcd-controller';
import HlsMock from '../../mocks/hls.mock';
import type { CMCDControllerConfig } from '../../../src/config';
import { CmcdHeaderField } from '@svta/common-media-library/cmcd/CmcdHeaderField';
import M3U8Parser from '../../../src/loader/m3u8-parser';
import { PlaylistLevelType } from '../../../src/types/loader';
import type { Fragment, Part } from '../../../src/loader/fragment';
import chai from 'chai';
const expect = chai.expect;
let cmcdController;
const url = 'https://dummy.url.com/playlist.m3u8';
const playlist = `#EXTM3U
#EXT-X-VERSION:7
#EXT-X-TARGETDURATION:2
#EXT-X-SERVER-CONTROL:CAN-BLOCK-RELOAD=YES,PART-HOLD-BACK=2.171
#EXT-X-PART-INF:PART-TARGET=1.034
#EXT-X-MAP:URI="https://dummy.url.com/18446744073709551615.m4s"
#EXT-X-MEDIA-SEQUENCE:10902
#EXT-X-PROGRAM-DATE-TIME:2024-05-02T18:03:57.020+00:00
#EXTINF:2,
https://dummy.url.com/10902.m4s
#EXT-X-PROGRAM-DATE-TIME:2024-05-02T18:03:59.020+00:00
#EXTINF:2,
https://dummy.url.com/10903.m4s
#EXT-X-PROGRAM-DATE-TIME:2024-05-02T18:04:01.020+00:00
#EXT-X-PART:DURATION=1,URI="https://dummy.url.com/10904.0.m4s"
#EXT-X-PART:DURATION=1,URI="https://dummy.url.com/10904.1.m4s"
#EXTINF:2,
https://dummy.url.com/10904.m4s
#EXT-X-PROGRAM-DATE-TIME:2024-05-02T18:04:03.020+00:00
#EXT-X-PART:DURATION=1,URI="https://dummy.url.com/10905.0.m4s",INDEPENDENT=YES
#EXT-X-PART:DURATION=1,URI="https://dummy.url.com/10905.1.m4s"
#EXTINF:2,
https://dummy.url.com/10905.m4s
#EXT-X-PROGRAM-DATE-TIME:2024-05-02T18:04:05.020+00:00
#EXT-X-PART:DURATION=1,URI="https://dummy.url.com/10906.0.m4s",INDEPENDENT=YES
#EXT-X-PART:DURATION=1,URI="https://dummy.url.com/10906.1.m4s"
#EXTINF:2,
https://dummy.url.com/10906.m4s`;
const details = M3U8Parser.parseLevelPlaylist(
playlist,
url,
0,
PlaylistLevelType.MAIN,
0,
null,
);
const uuidRegex =
'[a-f\\d]{8}-[a-f\\d]{4}-4[a-f\\d]{3}-[89ab][a-f\\d]{3}-[a-f\\d]{12}';
const level = {
bitrate: 1000,
details,
};
const setupEach = (cmcd?: CMCDControllerConfig) => {
const hls = new HlsMock({ cmcd }) as any;
hls.levelController = {
levels: [level],
level: 0,
};
hls.audioTracks = [];
cmcdController = new CMCDController(hls);
};
const base = {
url,
headers: undefined,
};
const applyPlaylistData = (data = {}) => {
const context = Object.assign(data, base);
cmcdController.applyPlaylistData(context);
return context;
};
const applyFragmentData = (frag: Fragment, part?: Part | undefined) => {
const context = Object.assign({ url: frag.url, frag, part });
cmcdController.applyFragmentData(context);
return context;
};
const expectField = (result, expected) => {
const regex = new RegExp(expected);
expect(regex.test(result)).to.equal(true);
};
describe('CMCDController', function () {
describe('cmcdController instance', function () {
describe('configuration', function () {
it('does not modify requests when disabled', function () {
setupEach();
const { config } = cmcdController.hls;
expect(config.pLoader).to.equal(undefined);
expect(config.fLoader).to.equal(undefined);
});
it('uses the session id if provided', function () {
const sessionId = 'SESSION_ID';
setupEach({ sessionId });
const { url } = applyPlaylistData();
expectField(url, `sid%3D%22${sessionId}%22`);
});
it('generates a session id if not provided', function () {
setupEach({});
const { url } = applyPlaylistData();
expectField(url, `sid%3D%22${uuidRegex}%22`);
});
it('uses the content id if provided', function () {
const contentId = 'CONTENT_ID';
setupEach({ contentId });
const { url } = applyPlaylistData();
expectField(url, `cid%3D%22${contentId}%22`);
});
it('uses headers if configured', function () {
const contentId = 'CONTENT_ID';
setupEach({ contentId, useHeaders: true });
const { url, headers = {} } = applyPlaylistData();
expect(url).to.equal(base.url);
expectField(headers[CmcdHeaderField.SESSION], `cid="${contentId}"`);
});
it('uses includeKeys if configured', function () {
const contentId = 'CONTENT_ID';
setupEach({ includeKeys: ['cid'], contentId });
const { url } = applyPlaylistData();
expect(url).to.equal(`${base.url}?CMCD=cid%3D%22${contentId}%22`);
});
it('uses fragment data', function () {
setupEach({});
const { url } = applyFragmentData(details.fragments[0]);
expectField(url, `nor%3D%2210903.m4s%22`);
expectField(url, `br%3D1`);
expectField(url, `d%3D2000`);
expectField(url, `ot%3Dav`);
});
it('uses part data when available', function () {
setupEach({});
const { url } = applyFragmentData(
details.fragments[2],
details.partList?.[0],
);
expectField(url, `nor%3D%2210904.1.m4s%22`);
expectField(url, `br%3D1`);
expectField(url, `d%3D1000`);
expectField(url, `ot%3Dav`);
});
});
});
});