Ctrl K
ComunidadGuías…

scripts/test_client_updates.mjs

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
// Run: node --experimental-vm-modules scripts/test_client_updates.mjs
import assert from 'node:assert/strict';
import { readFile } from 'node:fs/promises';
import vm from 'node:vm';

const events = new Map(), fields = new Map(), messages = [], urls = [];
function element() {
  const buttons = new Map();
  return { dataset: {}, style: {}, replaceChildren() { this.innerHTML = ''; },
    querySelectorAll() { return []; }, querySelector(key) { if (!buttons.has(key)) buttons.set(key, {}); return buttons.get(key); } };
}
const notice = element(); fields.set('client-update-notice', notice);
const document = { hidden: false, getElementById: id => fields.get(id), addEventListener: (key, fn) => events.set(key, fn) };
const window = { location: { search: '?tab=updates', assign: url => urls.push(url) },
  addEventListener: (key, fn) => events.set(key, fn), setInterval() {}, sessionStorage: { getItem: () => 'system', setItem() {} },
  AOEDE_CLIENT: { updater: 1 }, AoedeHost: { postMessage: message => messages.push(message) } };
const context = vm.createContext({ console, window, document, URLSearchParams, navigator: { userAgent: 'Android AoedeAndroid/1.1.9' },
  localStorage: { getItem() { throw Error('disabled'); }, setItem() { throw Error('disabled'); } },
  AbortController, setTimeout: (...args) => setTimeout(...args).unref(), clearTimeout });
const ui = new vm.SourceTextModule(await readFile('Aoede/assets/js/modules/ui.js', 'utf8'), { context });
await ui.link(() => new vm.SyntheticModule([], function () {}, { context })); await ui.evaluate();
const client = new vm.SourceTextModule(await readFile('Aoede/assets/js/modules/client_updates.js', 'utf8'), { context });
await client.link(() => ui); await client.evaluate();
const { newerVersion, currentClient, availableUpdate, renderClientUpdate, initClientUpdates } = client.namespace;
assert(newerVersion('1.10.0', '1.9.9')); assert(!newerVersion('1.1.9', '1.1.9')); assert(!newerVersion('bad', '1.1.9'));
assert.equal(currentClient('Mozilla Android Chrome/100'), null);
assert.equal(currentClient('AoedeAndroid/1.0.5').kind, 'android-wrapper');
assert.equal(currentClient('AoedeAndroid/1.1.9').kind, 'android-native');
const mac = currentClient('Macintosh Intel Mac OS X AoedeDesktop/1.2.8');
assert.equal(mac.arch, 'universal');
assert.equal(currentClient('Linux aarch64 AoedeDesktop/1.2.8').arch, 'arm64');
const data = { release: { tag: 'v1.2.7', downloads: [
  { client: 'android-native', platform: 'android', name: 'aoede-android.apk', version: '1.2.0', url: 'https://github.com/sPROFFEs/Aoede/releases/download/v1.2.7/aoede-android.apk' },
  { client: 'desktop', platform: 'mac', arch: 'universal', name: 'aoede-mac-universal.tar.gz', version: '1.2.9', url: 'https://github.com/sPROFFEs/Aoede/releases/download/v1.2.7/aoede-mac-universal.tar.gz' }
] } };
assert.equal(availableUpdate(data).version, '1.2.0');
const sameNameBuild = { release: { downloads: [{ ...data.release.downloads[0], version: '1.1.9', version_code: 16 }] } };
assert.equal(availableUpdate(sameNameBuild, { kind: 'android-native', version: '1.1.9', versionCode: 15 }).version_code, 16);
assert.equal(availableUpdate(sameNameBuild, { kind: 'android-native', version: '1.1.8', versionCode: 17 }), null); assert.equal(availableUpdate(data, mac).version, '1.2.9');
assert.equal(availableUpdate(data, { ...mac, version: '1.2.9' }), null);
assert.equal(availableUpdate(data, { ...mac, platform: 'win' }), null);
assert.equal(availableUpdate({ release: { downloads: [{ ...data.release.downloads[0], url: 'https://evil.test/a.apk' }] } }), null);
assert.equal(availableUpdate({ release: { downloads: [{ ...data.release.downloads[0], version: undefined }] } }), null);
renderClientUpdate(notice, data, true); assert.equal(notice.style.display, 'block');
notice.querySelector('[data-update-install]').onclick(); assert.deepEqual(messages, ['update']);
notice.querySelector('[data-update-dismiss]').onclick(); renderClientUpdate(notice, data, true); assert.equal(notice.style.display, 'none');
renderClientUpdate(notice, data); assert.equal(notice.style.display, 'block'); // About remains available after dismissing.
window.AOEDE_CLIENT = null; renderClientUpdate(notice, data); notice.querySelector('[data-update-install]').onclick();
assert.deepEqual(urls, [data.release.downloads[0].url]);
window.AOEDE_CLIENT = { updater: 1 }; initClientUpdates(); assert.equal(messages.at(-1), 'check-update');
events.get('aoede-client-update')({ detail: data }); assert.equal(notice.style.display, 'none'); // Still snoozed.

const nativeChecks = () => messages.filter(message => message === 'check-update').length;
const beforeCheck = nativeChecks();
context.fetch = async () => ({ ok: true, headers: { get: () => 'application/json' }, json: async () => data });
events.get('online')(); for (let i = 0; i < 10; i++) await Promise.resolve();
assert.equal(nativeChecks(), beforeCheck); // Shared backend cache avoids a GitHub request per device.
context.fetch = async () => ({ ok: false, status: 404 });
events.get('online')(); for (let i = 0; i < 10; i++) await Promise.resolve();
assert.equal(nativeChecks(), beforeCheck + 1); // An older backend still supports native checks.
assert.equal(availableUpdate({ release: { downloads: {} } }), null);
assert.equal(availableUpdate({ release: { downloads: [null] } }), null);

// A direct updater link takes precedence over a previously selected System tab.
const tabs = ['system', 'updates'].map(value => ({ ...element(), dataset: { settingsTab: value }, setAttribute(key, value) { this[key] = value; } }));
const panels = tabs.map(tab => ({ ...element(), dataset: { settingsPanel: tab.dataset.settingsTab }, setAttribute() {} }));
const root = { dataset: { settingsTabs: 'system' }, querySelectorAll: key => key === '[data-settings-tab]' ? tabs : panels, addEventListener() {} };
ui.namespace.initSettingsTabs({ querySelectorAll: () => [root] });
assert.equal(tabs[1]['aria-selected'], 'true'); assert.equal(panels[1].hidden, false);

const server = element(); fields.set('update-available-toast', server);
for (const id of ['update-toast-message', 'update-toast-dismiss', 'update-toast-open']) fields.set(id, element());
context.fetch = async () => ({ status: 200, ok: true, json: async () => ({ update_available: true, remote_full_commit: 'abcdef', remote_version: '1.2.8' }) });
const admin = new vm.SourceTextModule(await readFile('Aoede/assets/js/modules/admin_system.js', 'utf8'), { context });
await admin.link(name => {
  if (name === './ui.js') return ui;
  const exported = name.includes('admin_downloads') ? 'initDownloadManager' : name.includes('system_monitor') ? 'initSystemMonitor' : 'initUpdateProgress';
  return new vm.SyntheticModule([exported], function () { this.setExport(exported, () => {}); }, { context });
});
await admin.evaluate(); for (let i = 0; i < 10; i++) await Promise.resolve();
assert.equal(server.style.display, 'block'); fields.get('update-toast-open').onclick();
assert.equal(window.location.href, '/admin/system?tab=updates');
console.log('Client updates: channel/version matching, untrusted metadata, storage failure, native/manual actions, snooze and updater deep link passed');