Files
qqq-android/scripts/ci/scanner-echo-guards.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

95 lines
6.1 KiB
JavaScript

// Copyright (c) 2026 QRunIO. SPDX-License-Identifier: Apache-2.0
import assert from 'node:assert/strict';
import test from 'node:test';
import { runScannerE2e, validateEchoFixtureDescriptor, validateEchoFixtureContract, physicalSerialMatches, redactEchoOutput } from './scanner-e2e-runner.mjs';
const fixture = {
fixtureType: 'echo-ui-e2e', simulatedPrinting: true, baseUrl: 'http://127.0.0.1:49152/',
username: 'scanner', password: 'synthetic-scanner-password', adminUsername: 'admin', adminPassword: 'synthetic-admin-password',
};
const version = { apiVersion: '1.10.0', extensions: [{ name: 'mobile-scanners', version: '1.4.0', pathPrefix: '/qbit/mobile-scanners' }] };
const branding = { displayName: 'Echo UI E2E — simulated printing' };
/*******************************************************************************
** Identity is checked before evidence redaction; only the comparison escapes.
*******************************************************************************/
test('raw serial matches exactly and a different physical device is refused', () => {
const serial = 'SYNTHETIC_TC22_SERIAL';
assert.equal(physicalSerialMatches(serial, `${serial}\r\n`), true);
assert.equal(physicalSerialMatches(serial, 'ANOTHER_DEVICE\r\n'), false);
assert.equal(physicalSerialMatches(serial, redactEchoOutput(`${serial}\r\n`, serial)), false);
});
/*******************************************************************************
** Serial-bearing successful output and errors remain safe to persist as reports.
*******************************************************************************/
test('Echo artifacts and error text contain no selected serial', () => {
const serial = 'SYNTHETIC_TC22_SERIAL';
const artifact = JSON.stringify({ output: redactEchoOutput(`${serial}\nOK (1 test)`, serial),
error: redactEchoOutput(`Device ${serial} failed`, serial) });
assert.equal(artifact.includes(serial), false);
assert.equal(artifact.includes('<DEVICE>'), true);
});
/*******************************************************************************
** A valid descriptor is inspected without contacting a server or any device.
*******************************************************************************/
test('explicit simulated Echo descriptor is accepted', () => {
assert.equal(validateEchoFixtureDescriptor(fixture).port, '49152');
validateEchoFixtureContract(version, branding);
validateEchoFixtureContract({ ...version, extensions: [{ ...version.extensions[0], version: '1.5.0' }] }, branding);
});
/*******************************************************************************
** Existing scanner and printing listeners can never become this test's target.
*******************************************************************************/
test('all protected normal and printing ports are refused', () => {
for (const port of [57832, 51631, 18081, 18086]) {
assert.throws(() => validateEchoFixtureDescriptor({ ...fixture, baseUrl: `http://127.0.0.1:${port}/` }));
}
});
/*******************************************************************************
** URL decoration cannot turn the private fixture into another host or route.
*******************************************************************************/
test('non-private or decorated fixture origins are refused', () => {
for (const baseUrl of ['https://127.0.0.1:49152/', 'http://example.invalid:49152/', 'http://127.0.0.1/',
'http://127.0.0.1:49152/path', 'http://127.0.0.1:49152/?value=1', 'http://127.0.0.1:49152/#fragment',
'http://scanner:secret@127.0.0.1:49152/', 'not-a-url']) {
assert.throws(() => validateEchoFixtureDescriptor({ ...fixture, baseUrl }));
}
});
/*******************************************************************************
** Purpose, simulation and private account declarations must all match exactly.
*******************************************************************************/
test('wrong-purpose or incomplete credentials are refused', () => {
for (const changed of [{ fixtureType: 'device-screen-qa' }, { simulatedPrinting: false }, { simulatedPrinting: 'true' },
{ username: 'operator' }, { adminUsername: 'owner' }, { password: null }, { password: 'short' }, { adminPassword: 'short' }]) {
assert.throws(() => validateEchoFixtureDescriptor({ ...fixture, ...changed }));
}
});
/*******************************************************************************
** Another real demo or an unqualified contract cannot masquerade as this fixture.
*******************************************************************************/
test('contract and simulated branding must match the prepared cohort', () => {
assert.throws(() => validateEchoFixtureContract({ ...version, apiVersion: '1.9.0' }, branding));
assert.throws(() => validateEchoFixtureContract({ ...version, extensions: [{ ...version.extensions[0], version: '1.3.0' }] }, branding));
for (const unsupported of ['1.6.0', '1.5.1', '1.4.1', '2.0.0', '1.10.0', '1.5']) {
assert.throws(() => validateEchoFixtureContract({ ...version, extensions: [{ ...version.extensions[0], version: unsupported }] }, branding));
}
assert.throws(() => validateEchoFixtureContract({ ...version, extensions: [{ ...version.extensions[0], version: '1.5.0', pathPrefix: '/other' }] }, branding));
assert.throws(() => validateEchoFixtureContract(version, { displayName: 'QQQ Mobile' }));
});
/*******************************************************************************
** These calls stop before reading a fixture or spawning ADB, even when selected
** through the generic emulator entry or an incomplete physical invocation.
*******************************************************************************/
test('Echo requires its explicit physical entry inputs before device operations', async () => {
await assert.rejects(runScannerE2e({ scenario: 'echo' }), /explicit TC22/);
await assert.rejects(runScannerE2e({ scenario: 'echo', physical: true, fixture: 'unused.json' }), /explicit TC22/);
await assert.rejects(runScannerE2e({ scenario: 'echo', physical: true, fixture: 'unused.json', serial: 'emulator-5554' }), /explicit TC22/);
});