Skip to content

Commit e478731

Browse files
authored
feat: Stitcher tests (#12)
1 parent c2299c3 commit e478731

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

62 files changed

+533
-205
lines changed

packages/stitcher/src/extern/hls-parser/stringify.ts renamed to packages/stitcher/extern/hls-parser/stringify.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import {
1313
SpliceInfo,
1414
Variant,
1515
PostProcess,
16-
} from "./types";
16+
} from "./types.js";
1717

1818
const ALLOW_REDUNDANCY = [
1919
"#EXTINF",

packages/stitcher/jest.config.js

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,22 @@
1+
import { createRequire } from "node:module";
2+
3+
const require = createRequire(import.meta.url);
4+
15
/** @type {import('ts-jest').JestConfigWithTsJest} **/
26
export default {
3-
testEnvironment: "node",
4-
transform: {
5-
"^.+.tsx?$": ["ts-jest", {}],
6-
},
77
moduleNameMapper: {
8-
"^@src(.*)$": "<rootDir>/src$1",
8+
"^(\\.{1,2}/.*)\\.js$": "$1",
9+
"@mixwave/artisan/producer":
10+
"<rootDir>/test/mocks/import-artisan-producer.ts",
11+
},
12+
extensionsToTreatAsEsm: [".ts"],
13+
transform: {
14+
"^.+\\.(mt|t|cj|j)s$": [
15+
"ts-jest",
16+
{
17+
useESM: true,
18+
},
19+
],
920
},
10-
resolver: "jest-ts-webcompat-resolver",
21+
prettierPath: require.resolve("jest-prettier"),
1122
};

packages/stitcher/package.json

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,23 @@
55
"build": "tsc",
66
"start": "node ./dist/index.js",
77
"dev": "tsc-watch --noClear --onSuccess \"node ./dist/index.js\"",
8-
"test": "jest"
8+
"test": "NODE_OPTIONS=--experimental-vm-modules jest"
99
},
1010
"devDependencies": {
11+
"@babel/preset-env": "^7.25.4",
1112
"@jest/globals": "^29.7.0",
1213
"@types/find-config": "^1.0.4",
1314
"@types/hh-mm-ss": "^1.2.3",
1415
"@types/node": "^22.1.0",
1516
"@types/parse-filepath": "^1.0.2",
1617
"@types/uuid": "^10.0.0",
18+
"esm": "^3.2.25",
1719
"fetch-mock": "^11.1.1",
1820
"jest": "^29.7.0",
19-
"jest-ts-webcompat-resolver": "^1.0.0",
2021
"ts-jest": "^29.2.5",
2122
"tsc-watch": "^6.2.0",
22-
"typescript": "^5.5.4"
23+
"typescript": "^5.5.4",
24+
"jest-prettier": "npm:prettier@^2"
2325
},
2426
"dependencies": {
2527
"@fastify/cors": "^9.0.1",

packages/stitcher/src/playlist.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import { parse, stringify } from "./extern/hls-parser/index.js";
1+
import { parse, stringify } from "../extern/hls-parser/index.js";
22
import parseFilepath from "parse-filepath";
3-
import { Interstitial } from "./extern/hls-parser/types.js";
3+
import { Interstitial } from "../extern/hls-parser/types.js";
44
import { env } from "./env.js";
5-
import { MasterPlaylist, MediaPlaylist } from "./extern/hls-parser/types.js";
5+
import { MasterPlaylist, MediaPlaylist } from "../extern/hls-parser/types.js";
66
import type { Session } from "./types.js";
77

88
async function fetchPlaylist<T>(url: string) {

packages/stitcher/src/session.ts

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
import { client } from "./redis.js";
22
import { randomUUID } from "crypto";
3-
import { getInterstitialsFromVmap } from "./vmap.js";
3+
import { extractInterstitialFromVmapAdbreak } from "./vast.js";
4+
import { getVmap } from "./vmap.js";
45
import type { Session, Interstitial } from "./types.js";
56

67
const REDIS_PREFIX = `stitcher:session`;
78

8-
const key = (sessionId: string) => `${REDIS_PREFIX}:${sessionId}`;
9+
function getRedisKey(sessionId: string) {
10+
return `${REDIS_PREFIX}:${sessionId}`;
11+
}
912

1013
export async function createSession(data: {
1114
assetId: string;
@@ -18,8 +21,15 @@ export async function createSession(data: {
1821
const interstitials: Interstitial[] = [];
1922

2023
if (data.vmapUrl) {
21-
interstitials.push(...(await getInterstitialsFromVmap(data.vmapUrl)));
24+
const vmap = await getVmap(data.vmapUrl);
25+
26+
for (const adBreak of vmap.adBreaks) {
27+
interstitials.push(
28+
...(await extractInterstitialFromVmapAdbreak(adBreak)),
29+
);
30+
}
2231
}
32+
2333
if (data.interstitials) {
2434
interstitials.push(...data.interstitials);
2535
}
@@ -38,17 +48,21 @@ export async function createSession(data: {
3848
interstitials,
3949
} satisfies Session;
4050

41-
await client.json.set(key(sessionId), `$`, session);
51+
const redisKey = getRedisKey(sessionId);
4252

43-
await client.expire(key(sessionId), 60 * 60 * 6);
53+
await client.json.set(redisKey, `$`, session);
54+
await client.expire(redisKey, 60 * 60 * 6);
4455

4556
return session;
4657
}
4758

4859
export async function getSession(sessionId: string) {
49-
const data = await client.json.get(key(sessionId));
60+
const redisKey = getRedisKey(sessionId);
61+
62+
const data = await client.json.get(redisKey);
5063
if (!data) {
5164
throw new Error("No session found for id");
5265
}
66+
5367
return data as Session;
5468
}

packages/stitcher/src/vast.ts

Lines changed: 22 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,30 @@
11
import { env } from "./env.js";
22
import { addTranscodeJob } from "@mixwave/artisan/producer";
3-
import { VASTClient } from "./extern/vast-client/index.js";
3+
import { VASTClient } from "../extern/vast-client/index.js";
44
import { DOMParser } from "@xmldom/xmldom";
55
import * as uuid from "uuid";
66
import { NAMESPACE_UUID_AD } from "./const.js";
7-
import type { VmapAdBreak, VmapResponse } from "./vmap.js";
7+
import type { VmapAdBreak } from "./vmap.js";
88
import type { Interstitial } from "./types.js";
99
import type {
1010
VastResponse,
1111
VastCreativeLinear,
1212
VastAd,
13-
} from "./extern/vast-client/index.js";
13+
} from "../extern/vast-client/index.js";
1414

15-
export async function extractInterstitialsFromVmap(vmapResponse: VmapResponse) {
15+
export async function extractInterstitialFromVmapAdbreak(adBreak: VmapAdBreak) {
1616
const interstitials: Interstitial[] = [];
1717

18-
for (const adBreak of vmapResponse.adBreaks) {
19-
const adMedias = await getAdMedias(adBreak);
20-
21-
for (const adMedia of adMedias) {
22-
if (await isPackaged(adMedia.assetId)) {
23-
interstitials.push({
24-
timeOffset: adBreak.timeOffset,
25-
assetId: adMedia.assetId,
26-
});
27-
} else {
28-
scheduleForPackage(adMedia);
29-
}
18+
const adMedias = await getAdMedias(adBreak);
19+
20+
for (const adMedia of adMedias) {
21+
if (await isPackaged(adMedia.assetId)) {
22+
interstitials.push({
23+
timeOffset: adBreak.timeOffset,
24+
assetId: adMedia.assetId,
25+
});
26+
} else {
27+
scheduleForPackage(adMedia);
3028
}
3129
}
3230

@@ -138,10 +136,14 @@ function getCreative(ad: VastAd) {
138136
}
139137

140138
function getAdId(creative: VastCreativeLinear) {
141-
// Do not change this, or we'll have a mismatch between the already encoded ad's and the other.
142-
// See https://iabtechlab.com/guidance-for-uniquely-identifying-creative-asset-ids-in-vast-2/
143-
const adId = [creative.adId, creative.id].join(".");
144-
return uuid.v5(adId, NAMESPACE_UUID_AD);
139+
if (creative.adId && creative.id) {
140+
// Do not change this, or we'll have a mismatch between the already encoded ad's and the other.
141+
// See https://iabtechlab.com/guidance-for-uniquely-identifying-creative-asset-ids-in-vast-2/
142+
const adId = [creative.adId, creative.id].join(".");
143+
return uuid.v5(adId, NAMESPACE_UUID_AD);
144+
}
145+
146+
throw new Error("Failed to generate adId");
145147
}
146148

147149
type AdMedia = {

packages/stitcher/src/vmap.ts

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,10 @@
11
import { DOMParser, XMLSerializer } from "@xmldom/xmldom";
2-
import { extractInterstitialsFromVmap } from "./vast.js";
3-
import timeFormat from "hh-mm-ss";
2+
import * as timeFormat from "hh-mm-ss";
43

54
const USER_AGENT =
65
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/38.0.2125.111 Safari/537.36";
76

8-
export async function getInterstitialsFromVmap(url: string) {
9-
const vmap = await getVmap(url);
10-
return await extractInterstitialsFromVmap(vmap);
11-
}
12-
13-
async function getVmap(url: string): Promise<VmapResponse> {
7+
export async function getVmap(url: string): Promise<VmapResponse> {
148
const doc = await getXml(url);
159
const rootElement = doc.documentElement;
1610

packages/stitcher/test/__snapshots__/playlist.test.ts.snap

Lines changed: 0 additions & 7 deletions
This file was deleted.
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { jest } from "@jest/globals";
2+
3+
export const addTranscodeJob = jest.fn();

0 commit comments

Comments
 (0)