#!/usr/bin/env node // Exercise the shipped 0.29.7 backend monitor editor with a minimal DOM double. // This checks browser form behavior, not health probing or compiled Rust code. import fs from 'node:fs'; import vm from 'node:vm'; const root = process.cwd(); const core = fs.readFileSync(`${root}/src/admin/web/vhosts/core.js`, 'utf8'); const editor = fs.readFileSync(`${root}/src/admin/web/vhosts/editor.js`, 'utf8'); const settings = fs.readFileSync(`${root}/src/admin/web/app/settings.js`, 'utf8'); const html = fs.readFileSync(`${root}/src/admin/web/index.html`, 'utf8'); const start = core.indexOf(' function memberRow('); const end = core.indexOf(' function countryRateEditor(', start); if (start < 0 || end <= start) throw new Error('Backend editor code not found'); const check = (condition, reason) => { if (!condition) throw new Error(reason); }; class Element { constructor(tag = 'div', value = '', css = '') { this.tag = tag; this.value = value == null ? '' : String(value); this.checked = false; this.hidden = false; this.disabled = false; this.name = ''; this.children = []; this.listeners = new Map(); this.classes = new Set(css.split(/\s+/).filter(Boolean)); this.textContent = this.value; } append(...children) { for (const child of children) { child.parent = this; this.children.push(child); } } addEventListener(type, handler) { this.listeners.set(type, handler); } dispatch(type) { this.listeners.get(type)?.(); } querySelectorAll() { const found = []; const walk = node => { for (const child of node.children) { if (child.tag === 'input' || child.tag === 'select') found.push(child); walk(child); }}; walk(this); return found; } } const el = (tag, value, css) => new Element(tag, value, css); const text = (tag, label, css) => el(tag, label, css); const field = (parent, name, label, value, type = 'text') => { const wrapper = el('label'); const input = el(type === 'select' ? 'select' : 'input', value); input.name = name; input.checked = !!value; wrapper.append(input); parent.append(wrapper); return input; }; const button = (label, handler) => { const node = el('button', label); node.addEventListener('click', handler); return node; }; const note = (parent, label) => { const node = text('span', label); parent.append(node); return node; }; const parent = el('div'); const value = { id: 'error_404', url: 'http://127.0.0.1:8282', weight: 1, enabled: true, pool_eligible: false, health_check_enabled: false, preserve_host: true, health_check: { mode: 'http', path: '/ready', host: 'errors.example.test', interval_ms: 10000, timeout_ms: 1000, slow_response_ms: 500, fall: 2, rise: 1, expected_status_min: 200, expected_status_max: 399 } }; vm.runInNewContext(`${core.slice(start, end)}\nmemberRow(parent, value);`, { parent, value, el, text, field, button, note, t: key => key, toast: () => {}, changed: () => {}, appConfirm: async () => true, adminListenerBackendCandidate: () => false, backendHealthEditorControl: null }); const row = parent.children[0]; const input = name => (name.startsWith('member-health-') && name !== 'member-health-enabled' ? row.memberHealthPanel : row).querySelectorAll().find(item => item.name === name); const fields = []; const visit = node => { if (node.classes.has('vh-backend-health-fields')) fields.push(node); for (const child of node.children) visit(child); }; visit(row.memberHealthPanel); check(fields.length === 1, 'Custom backend health panel not created'); check(input('member-pool').checked === false, 'Internal 404 backend accidentally joined balanced pool'); check(input('member-enabled').checked === true, 'Backend was disabled when monitoring was disabled'); check(input('member-health-enabled').checked === false, 'Explicit monitoring off not restored'); check(input('member-health-custom').checked === true, 'Custom health not restored'); check(input('member-health-mode').value === 'http', 'Member health mode lost'); check(input('member-health-path').value === '/ready', 'Member health path lost'); check(input('member-health-interval').value === '10000', 'Member check interval lost'); check(fields[0].hidden === false && input('member-health-path').disabled, 'Unmonitored backend settings should be retained but disabled'); input('member-health-custom').checked = false; input('member-health-custom').dispatch('change'); check(fields[0].hidden && input('member-health-path').disabled, 'Inherit switch did not hide/disable overrides'); input('member-health-custom').checked = true; input('member-health-custom').dispatch('change'); check(!fields[0].hidden && input('member-health-path').disabled, 'Unmonitored backend unexpectedly became editable'); input('member-health-enabled').checked = true; input('member-health-enabled').dispatch('change'); check(!input('member-health-path').disabled, 'Monitored custom fields failed to re-enable'); check(input('member-pool').checked === false, 'Monitoring switch changed balanced-pool eligibility'); for (const part of [ 'health_check_enabled: yes("member-health-enabled", row)', 'health_check: yes("member-health-custom", row.memberHealthPanel)', 'expose_only_hits: tri("vh-cache-only-hits")', ]) check((editor + core).includes(part), `Missing saved field: ${part}`); check(editor.includes('tri(cacheOptions, "vh-cache-only-hits", "cacheOnlyHits", cache.expose_only_hits)'), 'Vhost cache override does not roundtrip'); check(html.includes('id="cache-only-hits"') && settings.includes('expose_only_hits: $("cache-only-hits").checked'), 'Global cache checkbox does not save'); check(settings.includes('$("cache-only-hits").checked = !!c.expose_only_hits'), 'Global cache checkbox does not load'); console.log('PASS: 0.29.7 backend monitor and custom policy form state, independent pool status, global/vhost cache diagnostics toggles.');