@@ -0,0 +1,154 @@
|
||||
#!/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 })
|
||||
}
|
||||
Reference in New Issue
Block a user