155 lines
5.8 KiB
JavaScript
155 lines
5.8 KiB
JavaScript
#!/usr/bin/env node
|
|
// SPDX-License-Identifier: LicenseRef-ConusAI-Template-1.0
|
|
// Chromium + actual module/client, local synthetic HTTP. No provider/auth proof.
|
|
import assert from "node:assert/strict"
|
|
import { spawnSync } from "node:child_process"
|
|
import { createHash } from "node:crypto"
|
|
import { mkdir, mkdtemp, readdir, readFile, rm, writeFile } from "node:fs/promises"
|
|
import http from "node:http"
|
|
import os from "node:os"
|
|
import path from "node:path"
|
|
import { fileURLToPath } from "node:url"
|
|
import { chromium } from "@playwright/test"
|
|
|
|
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..")
|
|
const evidence = process.env.LIFECYCLE_BROWSER_EVIDENCE
|
|
if (!evidence) throw new Error("LIFECYCLE_BROWSER_EVIDENCE must name a new external directory")
|
|
await mkdir(evidence, { mode: 0o700 })
|
|
const temp = await mkdtemp(path.join(os.tmpdir(), "conusai-knowledge-browser-"))
|
|
const build = spawnSync(
|
|
"bun",
|
|
[
|
|
"build",
|
|
"test/component-browser/knowledge.fixture.tsx",
|
|
"--target",
|
|
"browser",
|
|
"--format",
|
|
"iife",
|
|
"--outdir",
|
|
temp,
|
|
"--entry-naming",
|
|
"fixture.[ext]",
|
|
],
|
|
{ cwd: root, encoding: "utf8" },
|
|
)
|
|
if (build.status !== 0) throw new Error(`fixture build failed: ${build.stdout}${build.stderr}`)
|
|
const assets = new Set(await readdir(temp))
|
|
const network = []
|
|
const errors = []
|
|
const consoleErrors = []
|
|
const server = http.createServer(async (req, res) => {
|
|
if (req.url === "/api/v1/knowledge.search") {
|
|
const scenario = req.headers["x-fixture-scenario"]
|
|
const status = scenario === "configuration" ? 503 : 200
|
|
const body =
|
|
status === 503
|
|
? { status: 503, code: "configuration_missing", title: "Configuration required" }
|
|
: {
|
|
results:
|
|
scenario === "revoked"
|
|
? []
|
|
: [
|
|
{
|
|
fileId: "synthetic-file",
|
|
filename: "synthetic-source.txt",
|
|
collectionName: "sources",
|
|
snippet: "A synthetic passage with a current source citation.",
|
|
score: 0.9,
|
|
chunk: 1,
|
|
page: 3,
|
|
blockId: "p3-b2",
|
|
chunkId: "22222222-2222-4222-8222-222222222222",
|
|
},
|
|
],
|
|
}
|
|
network.push({ method: req.method, path: req.url, scenario, status })
|
|
res.writeHead(status, { "content-type": "application/json" })
|
|
res.end(JSON.stringify(body))
|
|
return
|
|
}
|
|
if (req.url === "/favicon.ico") {
|
|
res.writeHead(204)
|
|
res.end()
|
|
return
|
|
}
|
|
if (req.url?.startsWith("/") && assets.has(req.url.slice(1))) {
|
|
res.writeHead(200, {
|
|
"content-type": req.url.endsWith(".css")
|
|
? "text/css"
|
|
: req.url.endsWith(".woff2")
|
|
? "font/woff2"
|
|
: "application/javascript",
|
|
})
|
|
res.end(await readFile(path.join(temp, req.url.slice(1))))
|
|
return
|
|
}
|
|
res.writeHead(200, { "content-type": "text/html" })
|
|
res.end(
|
|
'<!doctype html><html lang="en"><head><title>Knowledge lifecycle fixture</title><link rel="stylesheet" href="/fixture.css"></head><body><main id="root"></main><script src="/fixture.js"></script></body></html>',
|
|
)
|
|
})
|
|
await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve))
|
|
const browser = await chromium.launch({ headless: true })
|
|
try {
|
|
const page = await browser.newPage({ viewport: { width: 1200, height: 800 } })
|
|
page.on("pageerror", (error) => errors.push(error.message))
|
|
page.on("console", (message) => {
|
|
if (message.type() === "error") {
|
|
consoleErrors.push(message.text())
|
|
if (!/503/.test(message.text())) errors.push(message.text())
|
|
}
|
|
})
|
|
const base = `http://127.0.0.1:${server.address().port}`
|
|
for (const scenario of ["cited", "revoked", "configuration"]) {
|
|
await page.goto(`${base}/?scenario=${scenario}`)
|
|
await page.getByRole("searchbox", { name: "Question" }).fill("synthetic source")
|
|
await page.getByRole("button", { name: "Search", exact: true }).click()
|
|
if (scenario === "cited") {
|
|
await page.getByText("A synthetic passage with a current source citation.", { exact: true }).waitFor()
|
|
assert.match(await page.locator("ol").innerText(), /synthetic-source.txt.*result 1.*page 3/s)
|
|
await page.getByText("p3-b2", { exact: true }).waitFor()
|
|
} else if (scenario === "revoked") {
|
|
await page.getByText("No answers in this collection.", { exact: true }).waitFor()
|
|
assert.equal(await page.locator("blockquote").count(), 0)
|
|
} else {
|
|
await page.getByRole("alert").filter({ hasText: "Set CONUSAI_ENVIRONMENT" }).waitFor()
|
|
assert.equal(await page.locator("blockquote").count(), 0)
|
|
}
|
|
await page.screenshot({ path: path.join(evidence, `${scenario}.png`), fullPage: true })
|
|
}
|
|
assert.deepEqual(errors, [])
|
|
const sources = {}
|
|
for (const file of [
|
|
"modules/knowledge/ui/search-form.tsx",
|
|
"modules/knowledge/ui/labels.ts",
|
|
"modules/knowledge/contract/procedures.ts",
|
|
"test/component-browser/knowledge.fixture.tsx",
|
|
])
|
|
sources[file] = createHash("sha256")
|
|
.update(await readFile(path.join(root, file)))
|
|
.digest("hex")
|
|
await writeFile(
|
|
path.join(evidence, "result.json"),
|
|
JSON.stringify(
|
|
{
|
|
tier: "component-browser-synthetic-http",
|
|
sourceCommit:
|
|
process.env.LIFECYCLE_SOURCE_COMMIT ??
|
|
spawnSync("git", ["rev-parse", "HEAD"], { cwd: root, encoding: "utf8" }).stdout.trim(),
|
|
sources,
|
|
network,
|
|
consoleErrors,
|
|
unexpectedErrors: errors,
|
|
result: "passed",
|
|
},
|
|
null,
|
|
2,
|
|
),
|
|
)
|
|
console.log("Knowledge citations, revocation and configuration refusals passed; fixture browser only")
|
|
} finally {
|
|
await browser.close()
|
|
await new Promise((resolve) => server.close(resolve))
|
|
await rm(temp, { recursive: true, force: true })
|
|
}
|