Files
prop-ai-hr/mobile-uni/tests/app-packaging.test.mjs
T

163 lines
6.9 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import assert from 'node:assert/strict';
import { execFileSync } from 'node:child_process';
import { readFile } from 'node:fs/promises';
import { test } from 'node:test';
import { fileURLToPath } from 'node:url';
import { dirname, resolve } from 'node:path';
import {
renderAndroidPrivacy,
validateAndroidPrivacyDocument,
validateLegalUrlPair
} from '../scripts/app-legal-config.mjs';
const mobileRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..');
const readJson = async (path) => JSON.parse(await readFile(new URL(path, import.meta.url), 'utf8'));
const hbuilderx522Compiler = '3.0.0-alpha-5020220260725001';
test('App 图标与启动图映射均可由预检脚本验证', () => {
const output = execFileSync(process.execPath, ['scripts/verify-app-assets.mjs'], {
cwd: mobileRoot,
encoding: 'utf8'
});
assert.match(output, /App asset verification passed/);
});
// 缺少 App 平台编译器时 `uni build -p app` 不会报错,只会静默产出带 /h5/ 基路径的
// H5 空壳(无 app-service.js/app-renderjs.js),使 build:app 成为假绿,renderjs
// 实时对练代码实际从未被 App 编译器处理。
test('App 平台编译器已声明且与其他 uni 包同版本,避免 build:app 静默回落 H5', async () => {
const pkg = await readJson('../package.json');
const appPlus = pkg.dependencies?.['@dcloudio/uni-app-plus'];
assert.ok(appPlus, '缺少 @dcloudio/uni-app-plus,build:app 会静默产出 H5 空壳');
assert.equal(appPlus, hbuilderx522Compiler, '编译链必须与当前 HBuilderX 5.22 调试基座匹配');
assert.equal(appPlus, pkg.dependencies['@dcloudio/uni-app']);
assert.equal(appPlus, pkg.dependencies['@dcloudio/uni-h5']);
assert.equal(appPlus, pkg.devDependencies['@dcloudio/vite-plugin-uni']);
});
test('Android 自定义基座显式面向 Android 15 权限模型', async () => {
const manifest = await readJson('../src/manifest.json');
const android = manifest['app-plus']?.distribute?.android;
assert.equal(android?.packagename, 'com.yincheng.wygj');
assert.equal(android?.targetSdkVersion, 35, '不得回退到 DCloud 默认的 targetSdkVersion 28 兼容模式');
});
test('Android 启动阶段不主动索取设备信息或外部存储权限', async () => {
const manifest = await readJson('../src/manifest.json');
const android = manifest['app-plus']?.distribute?.android;
const modules = manifest['app-plus']?.modules;
assert.equal(manifest['app-plus']?.distribute?.splashscreen?.useOriginalMsgbox, true);
assert.equal(android?.permissionPhoneState?.request, 'none');
assert.equal(android?.permissionExternalStorage?.request, 'none');
assert.ok(Object.hasOwn(modules || {}, 'Camera'), '媒体功能仍需按用户操作动态申请相机权限');
assert.ok(Object.hasOwn(modules || {}, 'Record'), '语音功能仍需按用户操作动态申请录音权限');
});
test('正式法务地址必须使用不同的公网 HTTPS 页面', () => {
assert.throws(() => validateLegalUrlPair('', 'https://legal.example.cn/privacy'), /required/);
assert.throws(
() => validateLegalUrlPair('http://legal.example.cn/terms', 'https://legal.example.cn/privacy'),
/HTTPS/
);
assert.throws(
() => validateLegalUrlPair('https://127.0.0.1/terms', 'https://legal.example.cn/privacy'),
/public hostname/
);
assert.throws(
() => validateLegalUrlPair('https://legal.example.cn/document', 'https://legal.example.cn/document'),
/must be different/
);
assert.throws(
() => validateLegalUrlPair('https://198.18.0.1/terms', 'https://legal.example.cn/privacy'),
/public hostname/
);
assert.throws(
() => validateLegalUrlPair('https://legal.invalid/terms', 'https://legal.example.cn/privacy'),
/reserved hostname/
);
assert.doesNotThrow(() => validateLegalUrlPair(
'https://198.51.99.1/terms',
'https://fd-company.cn/privacy'
));
assert.doesNotThrow(() => validateLegalUrlPair(
'https://fd-company.cn/terms',
'https://fd-company.cn/privacy'
));
});
test('Android 隐私模板的首次与二次弹窗使用同一组最终链接', async () => {
const template = await readFile(new URL('../androidPrivacy.json.example', import.meta.url), 'utf8');
const rendered = renderAndroidPrivacy(
template,
'https://legal.company.cn/bangdao/terms',
'https://legal.company.cn/bangdao/privacy'
);
const privacy = JSON.parse(rendered);
assert.deepEqual(validateAndroidPrivacyDocument(privacy), {
termsUrl: 'https://legal.company.cn/bangdao/terms',
privacyUrl: 'https://legal.company.cn/bangdao/privacy'
});
assert.doesNotMatch(rendered, /__TERMS_URL__|__PRIVACY_URL__/);
privacy.second.message = privacy.second.message.replace('/privacy', '/other-privacy');
assert.throws(() => validateAndroidPrivacyDocument(privacy), /must use the same legal links/);
});
test('Android 隐私配置写入 uni-app CLI 输入目录并校验编译产物', async () => {
const configureScript = await readFile(
new URL('../scripts/configure-android-privacy.mjs', import.meta.url),
'utf8'
);
const verifier = await readFile(
new URL('../scripts/verify-app-assets.mjs', import.meta.url),
'utf8'
);
assert.match(configureScript, /resolve\(projectRoot, 'src', 'androidPrivacy\.json'\)/);
assert.match(verifier, /dist\/build\/app\/androidPrivacy\.json/);
});
test('App 登录页法务地址校验不依赖浏览器 URL 构造器', async () => {
const legalService = await readFile(
new URL('../src/services/legal.ts', import.meta.url),
'utf8'
);
assert.doesNotMatch(legalService, /new URL\(/);
assert.match(legalService, /\^https:\\\/\\\/\(\[\^\/\?#\]\+\)/);
assert.match(legalService, /legalLinksReady/);
});
test('APK 门禁区分自定义调试基座与内置业务资源的测试包', async () => {
const verifier = await readFile(
new URL('../scripts/verify-android-apk.ps1', import.meta.url),
'utf8'
);
assert.match(verifier, /ValidateSet\('custom-base', 'dcloud-test', 'release'\)/);
assert.match(verifier, /assets\\apps/);
assert.match(verifier, /bundled androidPrivacy\.json/);
assert.match(verifier, /DCloud custom base APK contains only the native debug runtime/);
assert.match(verifier, /dist\\build\\app\\app-service\.js/);
assert.match(verifier, /dist\\build\\app-plus\\app-service\.js/);
assert.match(verifier, /fixed test SMS code/);
assert.match(verifier, /'mobile number'\s*=/);
});
test('8 月 1 日 RC 脚本默认拒绝脏工作区且记录完整构建证据', async () => {
const script = await readFile(
new URL('../../scripts/prepare-aug1-rc.ps1', import.meta.url),
'utf8'
);
assert.match(script, /requires a clean worktree/);
assert.match(script, /AllowDirtyRehearsal/);
assert.match(script, /RC build changed the previously clean worktree/);
assert.match(script, /releaseEligible = \$releaseEligible/);
assert.match(script, /APK metadata, legal links, signature, content match and sensitive-value scan/);
});