release: prepare August 1 Android RC

This commit is contained in:
key
2026-07-29 00:35:15 +08:00
parent 9ae5f750c8
commit 9ea5d79a62
41 changed files with 2322 additions and 71 deletions
+130
View File
@@ -4,6 +4,11 @@ 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'));
@@ -30,3 +35,128 @@ test('App 平台编译器已声明且与其他 uni 包同版本,避免 build:a
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/);
});