Initial public release
GeoIntel release gates / Compile, test, contracts and builds (push) Successful in 1m49s
GeoIntel release gates / Python and npm vulnerability policy (push) Successful in 21s
GeoIntel release gates / Production AI image, SBOM and container scan (push) Successful in 5m39s
GeoIntel release gates / Deploy exact gated revision to Unraid (push) Failing after 58m43s
GeoIntel release gates / Compile, test, contracts and builds (push) Successful in 1m49s
GeoIntel release gates / Python and npm vulnerability policy (push) Successful in 21s
GeoIntel release gates / Production AI image, SBOM and container scan (push) Successful in 5m39s
GeoIntel release gates / Deploy exact gated revision to Unraid (push) Failing after 58m43s
This commit is contained in:
@@ -0,0 +1,158 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
cd "$ROOT"
|
||||
|
||||
BASE_URL="${1:-${GE_INTEL_BASE_URL:-http://localhost:1202}}"
|
||||
NODE_BIN="${NODE_BIN:-}"
|
||||
if [ -z "$NODE_BIN" ]; then
|
||||
for candidate in node node.exe; do
|
||||
if command -v "$candidate" >/dev/null 2>&1; then
|
||||
NODE_BIN="$candidate"
|
||||
break
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
if [ -z "$NODE_BIN" ]; then
|
||||
echo "Node.js is required for AI handoff interaction verification." >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
if ! "$NODE_BIN" --input-type=module -e "await import('playwright')" >/dev/null 2>&1; then
|
||||
cat >&2 <<'EOF'
|
||||
Playwright is required for AI handoff interaction verification but is not available to Node.
|
||||
Install or expose Playwright, then rerun:
|
||||
bash scripts/verify_ai_handoff_interactions.sh http://localhost:1202
|
||||
EOF
|
||||
exit 2
|
||||
fi
|
||||
|
||||
TMP_SCRIPT="$(mktemp "${TMPDIR:-/tmp}/geointel-ai-handoff-XXXXXX.mjs")"
|
||||
trap 'rm -f "$TMP_SCRIPT"' EXIT
|
||||
|
||||
cat >"$TMP_SCRIPT" <<'NODE'
|
||||
import { chromium, request } from 'playwright'
|
||||
|
||||
const baseUrl = (process.argv[2] || 'http://localhost:1202').replace(/\/$/, '')
|
||||
|
||||
function assert(condition, message) {
|
||||
if (!condition) {
|
||||
throw new Error(message)
|
||||
}
|
||||
}
|
||||
|
||||
async function readJson(response, label) {
|
||||
const text = await response.text()
|
||||
let payload
|
||||
try {
|
||||
payload = JSON.parse(text)
|
||||
} catch (error) {
|
||||
throw new Error(`${label} returned non-JSON response: ${text.slice(0, 250)}`)
|
||||
}
|
||||
if (!response.ok()) {
|
||||
throw new Error(`${label} failed with ${response.status()}: ${JSON.stringify(payload)}`)
|
||||
}
|
||||
return payload
|
||||
}
|
||||
|
||||
async function seedDemoTileManifest() {
|
||||
const api = await request.newContext({ baseURL: baseUrl })
|
||||
try {
|
||||
const demoResponse = await api.post('/api/v1/demo/workflow')
|
||||
const demoPayload = await readJson(demoResponse, 'demo workflow')
|
||||
assert(demoPayload?.data?.project_id, 'demo workflow did not return data.project_id')
|
||||
assert(demoPayload?.data?.raster_dataset_id, 'demo workflow did not return data.raster_dataset_id')
|
||||
|
||||
const projectId = demoPayload.data.project_id
|
||||
const rasterDatasetId = demoPayload.data.raster_dataset_id
|
||||
const tileResponse = await api.post(`/api/v1/projects/${projectId}/datasets/${rasterDatasetId}/raster/tile`, {
|
||||
data: {
|
||||
tile_size: 64,
|
||||
overlap: 0,
|
||||
output_name: 'ai_handoff_smoke_tiles',
|
||||
},
|
||||
})
|
||||
const tilePayload = await readJson(tileResponse, 'raster tile')
|
||||
const manifestPath = tilePayload?.data?.result_json?.manifest_path
|
||||
assert(manifestPath, 'raster tile response did not include manifest_path')
|
||||
return { projectId, rasterDatasetId, manifestPath }
|
||||
} finally {
|
||||
await api.dispose()
|
||||
}
|
||||
}
|
||||
|
||||
async function selectDataset(page, datasetId) {
|
||||
await page.locator('[data-testid="workspace-nav-data"]').click()
|
||||
const datasetButton = page.locator(`[data-testid="dataset-select-${datasetId}"]`)
|
||||
await datasetButton.waitFor({ state: 'visible', timeout: 15000 })
|
||||
await datasetButton.click()
|
||||
await page.locator('[data-testid="workbench-inspector-panel"]').getByText('Dataset', { exact: true }).click()
|
||||
}
|
||||
|
||||
async function assertNoRuntimeErrors(page, consoleErrors) {
|
||||
const bodyText = await page.locator('body').innerText()
|
||||
assert(!/TypeError|ReferenceError|Unhandled Runtime Error|Cannot read properties/i.test(bodyText), 'runtime error text is visible in the UI')
|
||||
assert(consoleErrors.length === 0, `browser console errors were emitted: ${consoleErrors.join('\n')}`)
|
||||
}
|
||||
|
||||
const { rasterDatasetId, manifestPath } = await seedDemoTileManifest()
|
||||
|
||||
const browser = await chromium.launch()
|
||||
const page = await browser.newPage({ viewport: { width: 1440, height: 920 } })
|
||||
const consoleErrors = []
|
||||
page.on('console', (message) => {
|
||||
if (message.type() === 'error') {
|
||||
consoleErrors.push(message.text())
|
||||
}
|
||||
})
|
||||
page.on('pageerror', (error) => {
|
||||
consoleErrors.push(error.message)
|
||||
})
|
||||
|
||||
try {
|
||||
await page.goto(baseUrl, { waitUntil: 'networkidle' })
|
||||
const demoLoadButton = page.getByRole('button', { name: /Load demo workflow/i })
|
||||
if (await demoLoadButton.isVisible({ timeout: 5000 }).catch(() => false)) {
|
||||
await demoLoadButton.click()
|
||||
}
|
||||
|
||||
await selectDataset(page, rasterDatasetId)
|
||||
const detectionHandoff = page.getByRole('button', { name: 'Use in Detection Lab' })
|
||||
await detectionHandoff.scrollIntoViewIfNeeded()
|
||||
await detectionHandoff.click()
|
||||
|
||||
await page.locator('[data-testid="workspace-nav-ai"]').waitFor({ state: 'visible', timeout: 10000 })
|
||||
const detectionPanel = page.locator('.detection-lab-shell')
|
||||
await detectionPanel.waitFor({ state: 'visible', timeout: 10000 })
|
||||
const detectionModelValue = await detectionPanel.locator('select').nth(1).inputValue()
|
||||
assert(detectionModelValue === 'yolo-configured', `detection model handoff mismatch: ${detectionModelValue}`)
|
||||
const detectionDatasetValue = await detectionPanel.getByLabel('Raster dataset').inputValue()
|
||||
assert(detectionDatasetValue === rasterDatasetId, `detection dataset handoff mismatch: ${detectionDatasetValue}`)
|
||||
const detectionManifestValue = await detectionPanel.getByPlaceholder('Raster tile manifest path').inputValue()
|
||||
assert(detectionManifestValue === manifestPath, `detection manifest handoff mismatch: ${detectionManifestValue}`)
|
||||
|
||||
await selectDataset(page, rasterDatasetId)
|
||||
const segmentationHandoff = page.getByRole('button', { name: 'Use in Segmentation Lab' })
|
||||
await segmentationHandoff.scrollIntoViewIfNeeded()
|
||||
await segmentationHandoff.click()
|
||||
|
||||
await page.locator('[data-testid="workspace-nav-ai"]').waitFor({ state: 'visible', timeout: 10000 })
|
||||
const segmentationPanel = page.locator('.segmentation-lab-shell')
|
||||
await segmentationPanel.waitFor({ state: 'visible', timeout: 10000 })
|
||||
const segmentationDatasetValue = await segmentationPanel.getByLabel('Raster dataset').inputValue()
|
||||
assert(segmentationDatasetValue === rasterDatasetId, `segmentation dataset handoff mismatch: ${segmentationDatasetValue}`)
|
||||
const segmentationManifestValue = await segmentationPanel.getByPlaceholder('Raster tile manifest path').inputValue()
|
||||
assert(segmentationManifestValue === manifestPath, `segmentation manifest handoff mismatch: ${segmentationManifestValue}`)
|
||||
|
||||
await assertNoRuntimeErrors(page, consoleErrors)
|
||||
console.log('AI handoff interaction verification passed')
|
||||
console.log(`Raster dataset: ${rasterDatasetId}`)
|
||||
console.log(`Manifest: ${manifestPath}`)
|
||||
} finally {
|
||||
await browser.close()
|
||||
}
|
||||
NODE
|
||||
|
||||
"$NODE_BIN" "$TMP_SCRIPT" "$BASE_URL"
|
||||
Reference in New Issue
Block a user