'use strict'; const { test } = require('node:test'), assert = require('node:assert/strict'), vm = require('node:vm'), fs = require('node:fs'), path = require('node:path'); function setup(my) { const module = { exports: {} }; vm.runInNewContext(fs.readFileSync(path.join(__dirname, '../utils/permissions.js'), 'utf8'), { module, Promise, Error, my }); return module.exports; } // 2026-11-01 真机实测(SDK 2.9.4):淘宝用带前缀的 scope.camera(裸名 camera 返回 5022 "Scope:camera信息不存在")。 // 所以先试 scope.camera,失败再退到裸名,两者都失败才拒绝,且拒绝后不再重复弹授权。 test('deny never loops: tries prefixed then bare once, then requires explicit settings', async () => { let granted = { 'scope.userInfo': true }, calls = [], openCalls = 0; const permissions = setup({ getSetting({ success }) { success({ authSetting: granted }); }, authorize({ scopes, success, fail }) { calls.push(scopes); fail({ error: 11, errorMessage: 'no such scope' }); }, openSetting({ success }) { openCalls++; granted['scope.camera'] = true; success({ authSetting: granted }); } }); await assert.rejects(permissions.ensure('scope.camera'), e => e.permission === 'scope.camera'); await assert.rejects(permissions.ensure('scope.camera'), e => e.permission === 'scope.camera'); assert.deepEqual(calls, ['scope.camera', 'camera'], '第二次不再尝试授权'); assert.equal(openCalls, 0); await permissions.open(); await permissions.ensure('scope.camera'); assert.equal(calls.length, 2, '授权设置里开启后不再调用 authorize'); assert.equal(openCalls, 1); }); test('prefixed scope is used first and succeeds without falling back', async () => { let calls = []; const permissions = setup({ getSetting({ success }) { success({ authSetting: {} }); }, authorize({ scopes, success }) { calls.push(scopes); success(); } }); await permissions.ensure('scope.camera'); assert.deepEqual(calls, ['scope.camera']); await permissions.ensure('scope.album'); assert.deepEqual(calls, ['scope.camera', 'scope.album']); }); test('existing denial does not reauthorize; short-form settings keys work', async () => { let calls = 0; const permissions = setup({ getSetting({ success }) { success({ authSetting: { camera: false, album: true } }); }, authorize() { calls++; } }); await assert.rejects(permissions.ensure('scope.camera')); await permissions.ensure('scope.album'); assert.equal(calls, 0); }); test('diagnose reports capability and authorization outcomes', async () => { const permissions = setup({ SDKVersion: '2.9.4', canIUse() { return true; }, getSetting({ success }) { success({ authSetting: { 'scope.camera': true } }); }, authorize({ scopes, success, fail }) { scopes === 'scope.camera' ? success() : fail({ errorCode: '5022' }); }, createCameraContext() {}, chooseImage() {}, getImageInfo() {}, createCanvasContext() {}, openSetting() {} }); const info = await permissions.diagnose(); const text = info.join('\n'); assert.match(text, /SDKVersion=2\.9\.4/); assert.match(text, /createCameraContext: typeof=function canIUse=yes/); assert.match(text, /authSetting=\{"scope\.camera":true\}/); assert.match(text, /authorize\(scope\.camera\)=ok/); }); test('cameraSupported requires the native camera API and canIUse', () => { assert.equal(setup({ canIUse: () => true }).cameraSupported(), false, '没有 createCameraContext 就不支持'); assert.equal(setup({ canIUse: () => true, createCameraContext() {} }).cameraSupported(), true); assert.equal(setup({ canIUse: (api) => api !== 'camera', createCameraContext() {} }).cameraSupported(), false); }); test('location and recording scopes never reach platform settings or authorization', async () => { let calls = 0; const permissions = setup({ getSetting() { calls++; }, authorize() { calls++; } }); for (const scope of ['scope.location', 'scope.userLocation', 'scope.record', 'scope.audioRecord', 'location', 'record']) { await assert.rejects(permissions.ensure(scope), /不需要此项授权/); } assert.equal(calls, 0); const app = JSON.parse(fs.readFileSync(path.join(__dirname, '../app.json'), 'utf8')); assert.deepEqual(Object.keys(app.permission), ['scope.camera']); });