84 lines
5.6 KiB
JavaScript
84 lines
5.6 KiB
JavaScript
import { createInterface } from "node:readline";
|
|
import { createRequire } from "node:module";
|
|
import assert from "node:assert/strict";
|
|
import { validateBaseUrl } from "../packages/harness/dist/index.js";
|
|
import { parseHls } from "../packages/harness/dist/media/hls.js";
|
|
|
|
// Read-only acceptance preflight. No signed URLs, auth tokens or subtitles are logged.
|
|
const require = createRequire(new URL("../packages/harness/package.json", import.meta.url));
|
|
const { createClient } = require("@supabase/supabase-js");
|
|
const dramaId = "06aa396d-175a-48ed-ad76-02786ef4d0a9";
|
|
const cmsBase = "https://swave.pencil-stub.com/swave-api/api/v1/cms/playback/";
|
|
const reader = createInterface({ input: process.stdin });
|
|
console.log("Provide CMS email/password/publishableKey via hidden stdin.");
|
|
let stage = "login";
|
|
async function boundedText(response) {
|
|
assert.equal(response.status, 200);
|
|
const stream = response.body.getReader();
|
|
const chunks = [];
|
|
let length = 0;
|
|
try {
|
|
for (;;) {
|
|
const {done,value} = await stream.read();
|
|
if (done) break;
|
|
length += value.byteLength;
|
|
assert(length <= 2 * 1024 * 1024);
|
|
chunks.push(value);
|
|
}
|
|
} finally { await stream.cancel().catch(() => {}); }
|
|
return Buffer.concat(chunks).toString("utf8");
|
|
}
|
|
try {
|
|
const input = JSON.parse((await reader[Symbol.asyncIterator]().next()).value);
|
|
reader.close();
|
|
const client = createClient("https://lmcysqgfhoxppwuorsmu.supabase.co", input.publishableKey, {
|
|
auth:{persistSession:false,autoRefreshToken:false,detectSessionInUrl:false},
|
|
global:{fetch:(url,init)=>fetch(url,{...init,redirect:"error",signal:AbortSignal.timeout(20_000)})},
|
|
});
|
|
const {data:auth,error:authError} = await client.auth.signInWithPassword({email:input.email,password:input.password});
|
|
input.email = ""; input.password = "";
|
|
assert(!authError && auth.session);
|
|
const {data:user,error:userError} = await client.auth.getUser(auth.session.access_token);
|
|
assert(!userError && user.user?.app_metadata.role === "admin");
|
|
stage = "inventory";
|
|
const {data:drama,error:dramaError} = await client.from("drama").select("id,title,language,total_episodes").eq("id",dramaId).single();
|
|
assert(!dramaError && drama.id === dramaId && drama.language === "en");
|
|
const {data:episodes,error:episodeError} = await client.from("episode").select("id,episode_no,drama_id").eq("drama_id",dramaId).order("episode_no").range(0,999);
|
|
assert(!episodeError && episodes.length === drama.total_episodes);
|
|
assert(episodes.every((e,i)=>e.drama_id===dramaId && e.episode_no===i+1));
|
|
const sampled = [episodes[0],episodes[Math.floor(episodes.length/2)],episodes.at(-1)];
|
|
const checks = [];
|
|
for (const episode of sampled) {
|
|
stage = `episode_${episode.episode_no}_signing`;
|
|
const headers = {Authorization:`Bearer ${auth.session.access_token}`};
|
|
const signedResponse = await fetch(cmsBase+episode.id,{headers,redirect:"error",signal:AbortSignal.timeout(20_000)});
|
|
const signed = JSON.parse(await boundedText(signedResponse));
|
|
const url = new URL(signed.url);
|
|
assert(url.protocol==="https:" && !url.username && !url.password && !url.hash && url.pathname.endsWith(".m3u8"));
|
|
await validateBaseUrl(url.origin);
|
|
assert(Date.parse(signed.expires_at)>Date.now());
|
|
stage = `episode_${episode.episode_no}_manifest`;
|
|
const manifest = await boundedText(await fetch(url,{redirect:"error",signal:AbortSignal.timeout(20_000)}));
|
|
assert(manifest.trimStart().startsWith("#EXTM3U"));
|
|
console.log(JSON.stringify({episodeNo:episode.episode_no,cdnOrigin:url.origin,master:manifest.includes("#EXT-X-STREAM-INF"),segments:(manifest.match(/#EXTINF:/g)??[]).length,encrypted:manifest.includes("#EXT-X-KEY"),byteranges:manifest.includes("#EXT-X-BYTERANGE"),fragmentedMp4:manifest.includes("#EXT-X-MAP")}));
|
|
const parsed = parseHls(manifest);
|
|
if (parsed.kind === "master") {
|
|
const child = new URL(parsed.variants[0], url);
|
|
console.log(JSON.stringify({sameOrigin:child.origin===url.origin,childSameQuery:!child.search||child.search===url.search,childWithinEpisode:child.pathname.startsWith(url.pathname.slice(0,url.pathname.lastIndexOf("/")+1)),childExtension:child.pathname.split(".").at(-1)}));
|
|
assert(child.origin === url.origin && child.pathname.startsWith(url.pathname.slice(0,url.pathname.lastIndexOf("/")+1)) && !child.username && !child.password && !child.hash);
|
|
console.log(JSON.stringify({queryKeys:[...child.searchParams.keys()],authShape:/^\d+-[a-zA-Z0-9]+-[a-zA-Z0-9]+-[a-fA-F0-9]+$/.test(child.searchParams.get("auth_key")??"")}));
|
|
const resolved = child;
|
|
const variant = await boundedText(await fetch(resolved,{redirect:"error",signal:AbortSignal.timeout(20_000)}));
|
|
console.log(JSON.stringify({tags:[...new Set(variant.split("\n").filter(line=>line.startsWith("#")).map(line=>line.split(":")[0]))],segments:(variant.match(/#EXTINF:/g)??[]).length,ends:variant.includes("#EXT-X-ENDLIST"),parseAccepted:(()=>{try{parseHls(variant);return true;}catch{return false;}})()}));
|
|
}
|
|
stage = `episode_${episode.episode_no}_subtitle`;
|
|
const subtitle = await boundedText(await fetch(cmsBase+episode.id+"/subtitles/en.vtt",{headers,redirect:"error",signal:AbortSignal.timeout(20_000)}));
|
|
assert(subtitle.trimStart().startsWith("WEBVTT") && subtitle.includes("-->"));
|
|
checks.push({episodeNo:episode.episode_no,manifestStatus:200,hlsHeader:true,englishWebVtt:true});
|
|
}
|
|
console.log(JSON.stringify({result:"PASS",scope:"CMS source preflight only",drama,checks,checkedAt:new Date().toISOString(),mediaDownloaded:false,modelContentSent:false,videoAnalysisComplete:false}));
|
|
} catch {
|
|
console.error(JSON.stringify({result:"FAIL",stage}));
|
|
process.exitCode = 1;
|
|
} finally { reader.close(); }
|