import test from 'node:test' import assert from 'node:assert/strict' import { mkdtemp, readFile, writeFile } from 'node:fs/promises' import { tmpdir } from 'node:os' import path from 'node:path' import { createSimAdminClient, normalizeInstance, summarizeInstanceSnapshot, redactInstance, loadConfig, addInstanceToConfig, updateInstanceInConfig, deleteInstanceFromConfig } from '../server/core.js' test('normalizeInstance supports passwordless and password-protected instances without exposing password', () => { const open = normalizeInstance({ id: 'open-1', name: '开放设备', url: 'http://127.0.0.1:3000/' }, 0) assert.equal(open.auth.mode, 'none') assert.equal(open.url, 'http://127.0.0.1:3000') const protectedOne = normalizeInstance({ id: 'locked', url: 'http://cpe.local', auth: { password: 'secret' } }, 1) assert.equal(protectedOne.auth.mode, 'password') assert.equal(protectedOne.auth.password, 'secret') assert.equal(redactInstance(protectedOne).auth.hasPassword, true) assert.equal(redactInstance(protectedOne).auth.password, undefined) }) test('client stores simadmin_session from login and sends it on later proxied requests', async () => { const calls = [] const fetchImpl = async (url, options = {}) => { calls.push({ url: String(url), options }) if (String(url).endsWith('/api/auth/status') && calls.length === 1) { return new Response(JSON.stringify({ data: { configured: true, authenticated: false, settings: { password_protection_enabled: true } } }), { status: 200, headers: { 'content-type': 'application/json' } }) } if (String(url).endsWith('/api/auth/login')) { return new Response(JSON.stringify({ success: true }), { status: 200, headers: { 'set-cookie': 'simadmin_session=abc123; HttpOnly; Path=/; Max-Age=86400' } }) } if (String(url).endsWith('/api/device')) { return new Response(JSON.stringify({ data: { model: 'RM500Q' } }), { status: 200, headers: { 'content-type': 'application/json' } }) } return new Response('{}', { status: 404 }) } const client = createSimAdminClient(normalizeInstance({ id: 'locked', url: 'http://sim.local', auth: { password: 'secret' } }, 0), { fetchImpl }) const auth = await client.ensureAuthenticated() assert.equal(auth.authenticated, true) const proxied = await client.fetchJson('/api/device') assert.equal(proxied.status, 200) assert.match(calls.at(-1).options.headers.cookie, /simadmin_session=abc123/) }) test('snapshot summary merges status from device, sim, network, sms and system endpoints', () => { const snapshot = summarizeInstanceSnapshot({ device: { data: { model: 'CPE X', imei: '123' } }, sim: { data: { iccid: '8986', operator: 'CMCC' } }, network: { data: { accessTechnology: 'LTE', registration: 'registered' } }, smsStats: { data: { total: 12, unread: 2 } }, data: { data: { enabled: true, connected: true } }, ota: { data: { current_version: '1.2.3' } }, }) assert.equal(snapshot.device.model, 'CPE X') assert.equal(snapshot.sim.iccid, '8986') assert.equal(snapshot.network.accessTechnology, 'LTE') assert.equal(snapshot.sms.total, 12) assert.equal(snapshot.data.connected, true) assert.equal(snapshot.ota.currentVersion, '1.2.3') }) test('config store can add update and delete instances while persisting json', async () => { const dir = await mkdtemp(path.join(tmpdir(), 'multi-simadmin-')) const configPath = path.join(dir, 'config.json') await writeFile(configPath, JSON.stringify({ server: { host: '127.0.0.1', port: 9999 }, instances: [] }, null, 2)) const config = await loadConfig({ configPath, defaultConfigPath: configPath }) const added = await addInstanceToConfig(config, { id: 'home-cpe', name: '家里 CPE', url: 'http://192.168.1.1/', auth: { password: 'pw' }, tags: ['home'] }) assert.equal(added.id, 'home-cpe') assert.equal(added.url, 'http://192.168.1.1') assert.equal(config.instances.length, 1) const updated = await updateInstanceInConfig(config, 'home-cpe', { name: '客厅 CPE', url: 'http://192.168.1.2', auth: { mode: 'none' }, description: 'main router' }) assert.equal(updated.name, '客厅 CPE') assert.equal(updated.auth.password, '') const persisted = JSON.parse(await readFile(configPath, 'utf8')) assert.equal(persisted.instances[0].name, '客厅 CPE') assert.equal(persisted.instances[0].url, 'http://192.168.1.2') const removed = await deleteInstanceFromConfig(config, 'home-cpe') assert.equal(removed.id, 'home-cpe') assert.equal(config.instances.length, 0) assert.deepEqual(JSON.parse(await readFile(configPath, 'utf8')).instances, []) })