@norskvideo/ctl-test-harness 0.1.2 → 0.1.4

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/daemon.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { pollUntil } from "./poll.js";
2
2
  import { makeStoreDir, makeTempDir, TEST_TMP_BASE } from "./temp-dir.js";
3
3
  export declare const HEALTH_TIMEOUT_MS = 120000;
4
+ export declare const DAEMON_READY_TIMEOUT_MS = 60000;
4
5
  export { makeStoreDir, makeTempDir, pollUntil, TEST_TMP_BASE };
5
6
  export declare function requireLicenseFile(): string;
6
7
  export declare function runCli(storeDir: string, ...args: string[]): Promise<{
package/daemon.js CHANGED
@@ -11,6 +11,15 @@ import { pollUntil } from "./poll.js";
11
11
  import { makeStoreDir, makeTempDir, TEST_TMP_BASE } from "./temp-dir.js";
12
12
  process.env.NORSK_CTL_NO_MEDIA_DOWNLOAD = "1";
13
13
  export const HEALTH_TIMEOUT_MS = 120_000;
14
+ // The daemon's readiness probe (/api/ready) cannot answer until `serve` has
15
+ // brought up its oauth2-proxy + nginx sidecar CONTAINERS — /api/* redirects
16
+ // through the proxy. On a cold or contended CI runner that bring-up finishes
17
+ // just past 10s (observed ~10.5s on the GPU box under nightly load), so a 10s
18
+ // window lost the photo-finish and failed every test at setup with "Daemon did
19
+ // not become ready ... Unable to connect". A healthy daemon still answers in
20
+ // under a second, so the wider window only costs wall-clock when startup is
21
+ // genuinely slow.
22
+ export const DAEMON_READY_TIMEOUT_MS = 60_000;
14
23
  export { makeStoreDir, makeTempDir, pollUntil, TEST_TMP_BASE };
15
24
  function splitLines(text) {
16
25
  return text
@@ -89,7 +98,7 @@ export function startDaemon(storeDir, options) {
89
98
  const ready = pollUntil(async () => {
90
99
  const res = await fetch(`http://localhost:${port}/api/ready`, { signal: AbortSignal.timeout(1000) });
91
100
  return res.ok;
92
- }, { timeoutMs: 10_000, intervalMs: 500, label: "Daemon did not become ready" });
101
+ }, { timeoutMs: DAEMON_READY_TIMEOUT_MS, intervalMs: 500, label: "Daemon did not become ready" });
93
102
  return { daemon, ready };
94
103
  }
95
104
  function runningContainers(names) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@norskvideo/ctl-test-harness",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  "./daemon": {
package/studio-state.d.ts CHANGED
@@ -8,6 +8,7 @@ export interface ComponentsResponse {
8
8
  totalComponents: number;
9
9
  }
10
10
  export declare function applyTestHost(rawUrl: string): string;
11
+ export declare function mediaDirectUrl(rawUrl: string): string;
11
12
  export declare function fetchComponents(studioHostPort: number): Promise<ComponentsResponse | null>;
12
13
  export declare function fetchComponentState(studioHostPort: number, componentId: string): Promise<unknown | null>;
13
14
  /** SRT-listener state shape — see
@@ -93,6 +94,7 @@ export declare function assertMultivariantHasRenditions(opts: {
93
94
  expectedRenditionLabels: readonly string[];
94
95
  timeoutMs?: number;
95
96
  intervalMs?: number;
97
+ resolveFetchUrl?: (url: string) => string;
96
98
  }): Promise<void>;
97
99
  export declare function assertNoUnmapped(opts: {
98
100
  studioHostPort: number;
package/studio-state.js CHANGED
@@ -47,6 +47,33 @@ export function applyTestHost(rawUrl) {
47
47
  }
48
48
  return rawUrl;
49
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
+ }
50
77
  export async function fetchComponents(studioHostPort) {
51
78
  const r = await fetch(`http://${STUDIO_HOST}:${studioHostPort}/live/api/components`, {
52
79
  signal: AbortSignal.timeout(5000),
@@ -182,6 +209,7 @@ export async function assertSrtConnected(opts) {
182
209
  * `#EXT-X-MEDIA:TYPE=AUDIO ... URI="<rendition>/norsk.m3u8"` line. The caller
183
210
  * passes the set of expected labels; the body must reference every one. */
184
211
  export async function assertMultivariantHasRenditions(opts) {
212
+ const resolveFetchUrl = opts.resolveFetchUrl ?? applyTestHost;
185
213
  let lastState = null;
186
214
  let lastBody = null;
187
215
  let lastFetchStatus = null;
@@ -193,7 +221,7 @@ export async function assertMultivariantHasRenditions(opts) {
193
221
  if (!state?.url)
194
222
  return false;
195
223
  try {
196
- const r = await fetch(applyTestHost(state.url), { signal: AbortSignal.timeout(5000) });
224
+ const r = await fetch(resolveFetchUrl(state.url), { signal: AbortSignal.timeout(5000) });
197
225
  lastFetchStatus = r.status;
198
226
  if (!r.ok)
199
227
  return false;