@norskvideo/ctl-test-harness 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@norskvideo/ctl-test-harness",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  "./daemon": {
package/studio-state.d.ts CHANGED
@@ -7,6 +7,8 @@ export interface ComponentsResponse {
7
7
  components: ComponentSummary[];
8
8
  totalComponents: number;
9
9
  }
10
+ export declare function applyTestHost(rawUrl: string): string;
11
+ export declare function mediaDirectUrl(rawUrl: string): string;
10
12
  export declare function fetchComponents(studioHostPort: number): Promise<ComponentsResponse | null>;
11
13
  export declare function fetchComponentState(studioHostPort: number, componentId: string): Promise<unknown | null>;
12
14
  /** SRT-listener state shape — see
@@ -92,6 +94,7 @@ export declare function assertMultivariantHasRenditions(opts: {
92
94
  expectedRenditionLabels: readonly string[];
93
95
  timeoutMs?: number;
94
96
  intervalMs?: number;
97
+ resolveFetchUrl?: (url: string) => string;
95
98
  }): Promise<void>;
96
99
  export declare function assertNoUnmapped(opts: {
97
100
  studioHostPort: number;
package/studio-state.js CHANGED
@@ -23,6 +23,57 @@
23
23
  // alias: set NORSK_TEST_HOST=host.docker.internal there. Daemon-port fetches are
24
24
  // deliberately left on localhost — the daemon runs in-process with the tests.
25
25
  const STUDIO_HOST = process.env.NORSK_TEST_HOST ?? "localhost";
26
+ // Studio reports absolute playback URLs (the HLS multivariant, WHIP publish
27
+ // endpoints, ...) with the host it was configured with — loopback in a local
28
+ // run. In DooD CI the test process is inside a container and cannot reach that
29
+ // loopback; the published port lives on the host-gateway alias. Callers that
30
+ // fetch a server-reported URL verbatim must route it through here so a loopback
31
+ // host gets swapped for NORSK_TEST_HOST. No env / non-loopback host / unparseable
32
+ // input all pass through unchanged. Read from the env at call time (not the
33
+ // module-level STUDIO_HOST) so tests can toggle it.
34
+ export function applyTestHost(rawUrl) {
35
+ const testHost = process.env.NORSK_TEST_HOST;
36
+ if (!testHost)
37
+ return rawUrl;
38
+ try {
39
+ const u = new URL(rawUrl);
40
+ if (u.hostname === "localhost" || u.hostname === "127.0.0.1" || u.hostname === "::1") {
41
+ u.hostname = testHost;
42
+ return u.toString();
43
+ }
44
+ }
45
+ catch {
46
+ // not a parseable absolute URL — leave it for the caller's own error path
47
+ }
48
+ return rawUrl;
49
+ }
50
+ // The default norsk media HTTP port inside the container. The daemon's nginx
51
+ // proxies `/instance/<id>/media/*` here; whip-driver hits the same port direct.
52
+ const MEDIA_HTTP_PORT = 8080;
53
+ // Rewrite a daemon-proxy-routed media URL to a direct hit on the Norsk media
54
+ // container over the daemon's `norsk-net` bridge. Studio advertises playback
55
+ // URLs as `http://<publicHost>/instance/<id>/media/<native>` — that route is
56
+ // served ONLY by the daemon's nginx proxy. A harness that runs proxy-less (and
57
+ // whose runner has joined norsk-net) can instead reach the media container
58
+ // directly by its compose service name: `http://<id>-media-1:8080/<native>`.
59
+ // The `/instance/<id>/media` prefix is the proxy's routing concern; Norsk's
60
+ // native path is what follows it. Falls back to applyTestHost for URLs that
61
+ // don't carry the proxy prefix. Mirrors whip-driver's dockerNetUrl.
62
+ export function mediaDirectUrl(rawUrl) {
63
+ try {
64
+ const u = new URL(rawUrl);
65
+ const m = u.pathname.match(/^\/instance\/([^/]+)\/media(\/.*)?$/);
66
+ if (m) {
67
+ const id = m[1];
68
+ const rest = m[2] ?? "/";
69
+ return `http://${id}-media-1:${MEDIA_HTTP_PORT}${rest}${u.search}`;
70
+ }
71
+ }
72
+ catch {
73
+ // not a parseable URL — fall through
74
+ }
75
+ return applyTestHost(rawUrl);
76
+ }
26
77
  export async function fetchComponents(studioHostPort) {
27
78
  const r = await fetch(`http://${STUDIO_HOST}:${studioHostPort}/live/api/components`, {
28
79
  signal: AbortSignal.timeout(5000),
@@ -158,6 +209,7 @@ export async function assertSrtConnected(opts) {
158
209
  * `#EXT-X-MEDIA:TYPE=AUDIO ... URI="<rendition>/norsk.m3u8"` line. The caller
159
210
  * passes the set of expected labels; the body must reference every one. */
160
211
  export async function assertMultivariantHasRenditions(opts) {
212
+ const resolveFetchUrl = opts.resolveFetchUrl ?? applyTestHost;
161
213
  let lastState = null;
162
214
  let lastBody = null;
163
215
  let lastFetchStatus = null;
@@ -169,7 +221,7 @@ export async function assertMultivariantHasRenditions(opts) {
169
221
  if (!state?.url)
170
222
  return false;
171
223
  try {
172
- const r = await fetch(state.url, { signal: AbortSignal.timeout(5000) });
224
+ const r = await fetch(resolveFetchUrl(state.url), { signal: AbortSignal.timeout(5000) });
173
225
  lastFetchStatus = r.status;
174
226
  if (!r.ok)
175
227
  return false;