Files
qqq-android/deployment/company-profile/generate-company-profile.test.mjs
T
james.maesandClaude Sonnet 5.5 f72b90d08c
QRun-IO/ci-policy: ci / fork-guard (pull_request) Skipped
QRun-IO/ci-policy: ci / head-profile (pull_request) Skipped
QRun-IO/ci-policy: ci / application-template-0 (pull_request) Skipped
QRun-IO/ci-policy: ci / gradle-1 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-docker-3 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-docker-4 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-frontend-browser-5 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-6 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-7 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-8 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-9 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-10 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-playwright-11 (pull_request) Skipped
QRun-IO/ci-policy: ci / repository-policy-12 (pull_request) Skipped
QRun-IO/ci-policy: ci / source-config-13 (pull_request) Skipped
QRun-IO/ci-policy: ci / source-config-14 (pull_request) Skipped
QRun-IO/ci-policy: ci / original-business (pull_request) Skipped
QRun-IO/ci-policy: ci / original-mcp (pull_request) Skipped
QRun-IO/ci-policy: ci / original-voyage (pull_request) Skipped
QRun-IO/ci-policy: ci / not-enrolled (pull_request) Skipped
QRun-IO/ci-policy: native / fork-deny (pull_request) Skipped
QRun-IO/ci-policy: native / dotnet-0 (pull_request) Skipped
QRun-IO/ci-policy: native / native-result (pull_request) Successful in 5s
QRun-IO/ci-policy: Shared Android CI / unit-test (pull_request) Successful in 4m25s
QRun-IO/ci-policy: ci / repository-policy (pull_request) Skipped
QRun-IO/ci-policy: ci / java-core-2-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-docker-3-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-6-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-7-head (pull_request) Skipped
QRun-IO/ci-policy: Shared Android CI / android-test (pull_request) Successful in 9m50s
QRun-IO/ci-policy: Shared Android CI / secrets (pull_request) Successful in 19s
QRun-IO/ci-policy: Shared Android CI / package (pull_request) Successful in 13s
QRun-IO/ci-policy: Shared Android CI / gate (pull_request) Successful in 12s
QRun-IO/ci-policy: ci / android-0 (pull_request) Successful in 15m3s
QRun-IO/ci-policy: ci / ci-result (pull_request) Successful in 4s
feat(scanner): complete device UX and Authentik acceptance harness
Add scan value and symbology feedback, optional status wallpaper and managed server/orientation configuration, proven AIM profile readback, and administrator-controlled viewing UI. Extend native Echo acceptance for per-scan copies and add an opt-in real-provider Authentik harness isolated to the disposable test package.

Verified: 222 JVM tests, lint with zero errors/warnings, debug/E2E/test APK builds, 23 Node tests and source standards. Updated TC22 Echo acceptance passes ten stages with simulated input/printing. Actual Authentik desktop acceptance passes; native Authentik and physical trigger/print validation remain pending. Normal TC22 updated with the matching signer and data preserved.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-03 15:07:44 -06:00

126 lines
6.1 KiB
JavaScript

/* Copyright (c) 2026 QRunIO. SPDX-License-Identifier: Apache-2.0 */
import assert from "node:assert/strict"
import { mkdtempSync, readFileSync, readdirSync } from "node:fs"
import { tmpdir } from "node:os"
import { join } from "node:path"
import test from "node:test"
import { EHS_PACKAGE, MANAGED_ORIENTATIONS, generate, managedServerUrl, validate, xml } from "./generate-company-profile.mjs"
const example = () => JSON.parse(readFileSync(new URL("./profile.example.json", import.meta.url), "utf8"))
const measured = () => {
const p = example()
Object.assign(p.device, { androidVersion: "14", mxVersion: "14.0.0.0", osBuild: "measured-build" })
p.ehs.installedVersion = "8.0"
p.qqq.endpoint = "https://qqq.company.test"
p.permittedApps.push({ label: "Camera", package: "com.example.camera", activity: null, icon: null })
p.deploy = [{ package: EHS_PACKAGE, action: "Install", apk: "/enterprise/usr/persist/EHS.apk" }]
return p
}
const out = () => mkdtempSync(join(tmpdir(), "company-profile-"))
test("example is valid but reports every unmeasured input and withholds MX output", () => {
const dir = out()
const report = generate(example(), dir)
assert.deepEqual(report.errors, [])
assert.equal(report.ready, false)
assert.ok(report.missing.some((m) => m.startsWith("device.mxVersion")))
assert.ok(report.missing.some((m) => m.startsWith("permittedApps")))
assert.ok(report.missing.some((m) => m.startsWith("qqq.endpoint")))
assert.deepEqual(readdirSync(dir).sort(), ["enterprisehomescreen.xml", "profile-report.json"])
})
test("measured profile is ready and emits documented EHS and MX elements only", () => {
const dir = out()
const report = generate(measured(), dir)
assert.deepEqual(report.errors, [])
assert.equal(report.ready, true)
const ehs = readFileSync(join(dir, "enterprisehomescreen.xml"), "utf8")
assert.match(ehs, /<title><value>Company \[serial_number\]<\/value><\/title>/)
assert.match(ehs, /<wallpaper>\/enterprise\/usr\/company\/wallpaper\.png<\/wallpaper>/)
assert.match(ehs, /<usb_debugging_disabled>1<\/usb_debugging_disabled>/)
assert.match(ehs, /<kiosk_mode_enabled>0<\/kiosk_mode_enabled>/)
assert.match(ehs, /<admin><\/admin>/)
const mx = readFileSync(join(dir, "stagenow-mx-appmgr.xml"), "utf8")
assert.match(mx, /value="SetDefaultLauncher"/)
assert.match(mx, new RegExp(`name="Package" value="${EHS_PACKAGE.replace(/\./g, "\\.")}"`))
assert.doesNotMatch(ehs, /<admin>[^<]+<\/admin>/)
assert.doesNotMatch(mx + ehs, /token|secret/i)
})
test("security-weakening or undocumented inputs are rejected", () => {
const cases = [
(p) => { p.permittedApps.push({ label: "All", package: "com.android.*" }) },
(p) => { p.permittedApps.push({ label: "Wild", package: "com.mobility.*" }) },
(p) => { p.launcher.wallpaper = "/sdcard/wallpaper.png" },
(p) => { p.launcher.wallpaper = "/enterprise/usr/../x.png" },
(p) => { p.qqq.endpoint = "http://qqq.company.test" },
(p) => { p.qqq.endpoint = "https://user:pw@qqq.company.test" },
(p) => { p.launcher.title = "Dynamic [username]" },
(p) => { p.restrictions.systemSettingsRestricted = 2 },
(p) => { delete p.restrictions.recentAppsAccessDisabled },
(p) => { p.launcher.iconSize = "XL" },
(p) => { p.permittedApps.push({ label: "A very long label over limit", package: "com.example.long" }) },
(p) => { p.permittedApps = [{ label: "Other", package: "com.example.other" }] },
(p) => { p.deploy = [{ package: "com.evil.app", action: "Install", apk: "/enterprise/usr/persist/x.apk" }] },
]
for (const mutate of cases) {
const p = measured()
mutate(p)
assert.ok(validate(p).errors.length > 0, mutate.toString())
}
})
test("managed configuration uses the app's exact keys and is withheld for a placeholder endpoint", () => {
const withheld = out()
const placeholder = generate(example(), withheld)
assert.ok(!readdirSync(withheld).includes("managed-configuration.json"))
assert.ok(placeholder.warnings.some((w) => w.startsWith("managed-configuration.json withheld")))
const dir = out()
const p = measured()
p.qqq.endpoint = " https://QQQ.Company.test "
p.qqq.orientation = "REVERSE_LANDSCAPE"
assert.deepEqual(generate(p, dir).errors, [])
assert.deepEqual(JSON.parse(readFileSync(join(dir, "managed-configuration.json"), "utf8")), {
qqq_server_url: "https://qqq.company.test/",
qqq_orientation: "REVERSE_LANDSCAPE",
})
const noOrientation = out()
generate(measured(), noOrientation)
assert.deepEqual(Object.keys(JSON.parse(readFileSync(join(noOrientation, "managed-configuration.json"), "utf8"))), ["qqq_server_url"])
})
test("managed server URL accepts only an https origin, mirroring the app", () => {
assert.equal(managedServerUrl("https://qqq.company.test"), "https://qqq.company.test/")
assert.equal(managedServerUrl("https://qqq.company.test:8443/"), "https://qqq.company.test:8443/")
for (const bad of ["http://qqq.company.test", "https://u:p@qqq.company.test", "https://qqq.company.test/app", "https://qqq.company.test/?x=1", "https://qqq.company.test/#f", "ftp://qqq.company.test", "nonsense", "", null, 7, `https://${"a".repeat(2100)}`]) {
assert.equal(managedServerUrl(bad), null, String(bad))
const p = measured()
p.qqq.endpoint = bad
assert.ok(validate(p).errors.length > 0, String(bad))
}
})
test("orientation must be an exact app value", () => {
for (const good of MANAGED_ORIENTATIONS) {
const p = measured()
p.qqq.orientation = good
assert.deepEqual(validate(p).errors, [])
}
for (const bad of ["portrait", "Landscape", "AUTO", "SENSOR", "", 90]) {
const p = measured()
p.qqq.orientation = bad
assert.ok(validate(p).errors.length > 0, String(bad))
}
})
test("XML special characters in labels are escaped", () => {
assert.equal(xml(`a&b<c>"d"'e'`), "a&amp;b&lt;c&gt;&quot;d&quot;&apos;e&apos;")
const p = measured()
p.permittedApps[1].label = "Pick & <Scan>"
const dir = out()
assert.deepEqual(generate(p, dir).errors, [])
assert.match(readFileSync(join(dir, "enterprisehomescreen.xml"), "utf8"), /label="Pick &amp; &lt;Scan&gt;"/)
})