Files
openless/openless-all/app/scripts/android-insert-tier-fallback-contract.test.mjs

107 lines
3.6 KiB
JavaScript

#!/usr/bin/env node
import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
const insertPath = fileURLToPath(new URL('../src-tauri/src/android/insert.rs', import.meta.url));
const tiersPath = fileURLToPath(
new URL('../src-tauri/src/android/insert_tiers.rs', import.meta.url),
);
const shizukuPath = fileURLToPath(new URL('../src-tauri/src/android/shizuku.rs', import.meta.url));
const bridgePath = fileURLToPath(
new URL('../android/kotlin/OpenLessShizukuBridge.kt', import.meta.url),
);
const clientPath = fileURLToPath(
new URL('../android/kotlin/OpenLessShizukuUserServiceClient.kt', import.meta.url),
);
function rustFunctionBody(source, functionSignature) {
const signatureIndex = source.indexOf(functionSignature);
assert.notEqual(signatureIndex, -1, `missing Rust function: ${functionSignature}`);
const openBrace = source.indexOf('{', signatureIndex);
assert.notEqual(openBrace, -1, `missing opening brace: ${functionSignature}`);
let depth = 0;
for (let index = openBrace; index < source.length; index += 1) {
if (source[index] === '{') depth += 1;
if (source[index] === '}') depth -= 1;
if (depth === 0) return source.slice(openBrace + 1, index);
}
assert.fail(`missing closing brace: ${functionSignature}`);
}
const insertSource = readFileSync(insertPath, 'utf8');
const tiersSource = readFileSync(tiersPath, 'utf8');
const shizukuSource = readFileSync(shizukuPath, 'utf8');
const bridgeSource = readFileSync(bridgePath, 'utf8');
const clientSource = readFileSync(clientPath, 'utf8');
const tieredFallbackBody = rustFunctionBody(insertSource, 'fn insert_with_tiered_fallback');
assert.match(
insertSource,
/insert_with_tiered_fallback/,
'android insert must use the tiered fallback entry point',
);
assert.match(
tieredFallbackBody,
/paste_via_accessibility_with_result\(text\)[\s\S]*paste_via_shizuku_with_result\(\)/s,
'tier1 accessibility must be attempted before tier2 shizuku',
);
assert.match(
tieredFallbackBody,
/paste_via_shizuku_with_result\(\)[\s\S]*resolve_tiered_insert_status/s,
'tier resolution must happen after shizuku',
);
assert.match(
tieredFallbackBody,
/paste_via_accessibility_with_result\(text\)[\s\S]*paste_via_shizuku_with_result\(\)[\s\S]*clipboard_fallback\(/s,
'clipboard fallback must run only after accessibility and shizuku attempts',
);
assert.match(
tiersSource,
/resolve_tiered_insert_status/,
'tier resolution helper must exist for clipboard fallback gating',
);
assert.match(
shizukuSource,
/paste_via_shizuku_with_result/,
'shizuku module must expose paste injection result',
);
assert.match(
tieredFallbackBody,
/tier2 skipped: tier1 succeeded/,
'tier2 must be skipped when tier1 already succeeded',
);
assert.match(
clientSource,
/processNameSuffix\(/,
'recovery service bind must set processNameSuffix',
);
assert.match(
clientSource,
/withPasteService/,
'shizuku client must expose daemon paste service bind',
);
assert.match(
clientSource,
/withPasteService[\s\S]*daemon\s*=\s*true[\s\S]*PASTE_SERVICE_PROCESS_SUFFIX/s,
'paste service bind must use daemon mode with required process suffix',
);
assert.match(
bridgeSource,
/fun injectPasteKey\(context: Context\): Boolean/,
'shizuku bridge must expose injectPasteKey',
);
assert.match(
bridgeSource,
/injectPasteKeyViaShizukuShell/,
'paste injection must try Shizuku shell before UserService bind',
);
assert.match(
bridgeSource,
/getDeclaredMethod\(\s*"newProcess"/,
'shell paste must invoke private Shizuku.newProcess via reflection',
);
console.log('Android insert tier fallback contract checks passed');