const test = require('node:test'); const assert = require('node:assert/strict'); const tabs = require('../frontend/web/static/js/workspace-tabs.js'); function storage() { const values = new Map(); return { getItem: key => values.get(key) || null, setItem: (key, value) => values.set(key, value), removeItem: key => values.delete(key) }; } const dashboard = { key: 'dashboard', path: '/', label: '대시보드' }; const users = { key: 'users', path: '/admin/users', label: '사용자 계정' }; const port = { key: 'old-port-key', path: '/ports/16/parameters', label: 'Port 16' }; test('tabs survive page navigation and refresh within the same login', () => { const data = storage(); const scope = () => ({id: 'login-1', user: 'admin', role: 'administrator'}); tabs.createStore(() => data, scope).write([dashboard, users]); assert.deepEqual(tabs.createStore(() => data, scope).read(), [dashboard, users]); }); test('administrator tabs never carry over to a port user login', () => { const data = storage(); let scope = {id: 'admin-login', user: 'admin', role: 'administrator'}; const store = tabs.createStore(() => data, () => scope); store.write([dashboard, users]); scope = {id: 'operator-login', user: 'operator', role: 'port_operator'}; assert.deepEqual(store.read(), []); store.write([dashboard, users, port]); assert.deepEqual(store.read().map(tab => tab.path), ['/', '/ports/16/parameters']); }); test('logging in again as the same user resets previous tabs', () => { const data = storage(); let scope = {id: 'first-login', user: 'operator', role: 'port_operator'}; const store = tabs.createStore(() => data, () => scope); store.write([dashboard, port]); scope = {...scope, id: 'second-login'}; assert.deepEqual(store.read(), []); }); test('role or account changes also invalidate a stale scope', () => { for (const changed of [{user: 'someone-else'}, {role: 'port_operator'}]) { const data = storage(); let scope = {id: 'login', user: 'admin', role: 'administrator'}; const store = tabs.createStore(() => data, () => scope); store.write([dashboard, users]); scope = {...scope, ...changed}; assert.deepEqual(store.read(), []); } }); test('closing tabs persists; logout clears the store', () => { const data = storage(); const store = tabs.createStore(() => data, () => ({id: 'login', user: 'admin', role: 'administrator'})); store.write([dashboard, users]); store.write(store.read().filter(tab => tab.key !== users.key)); assert.deepEqual(store.read(), [dashboard]); store.clear(); assert.deepEqual(store.read(), []); assert.equal(data.getItem(tabs.storageKey), null); }); test('operator tabs reject admin/restart routes and malformed or external URLs', () => { for (const path of ['/admin/users', '/network/interface-1', '/port-debug', '/ports/restart', '/ports/49/parameters', '/ports/0/advanced', 'https://example.com', '//example.com', 'javascript:alert(1)', '/api/v1/auth/users', '/ports/../admin/users', '/logout', '/login', '/ports/1/parameters?unsafe=1']) { assert.equal(tabs.allowedPath(path, 'port_operator'), false, path); } for (const path of ['/', '/port-status', '/password', '/ports/1/parameters', '/ports/48/advanced']) { assert.equal(tabs.allowedPath(path, 'port_operator'), true, path); } }); test('legacy/malformed records and absent workspace identities are not restored', () => { const data = storage(); const scope = () => ({id: 'login', user: 'operator', role: 'port_operator'}); for (const record of ['bad json', JSON.stringify([dashboard, users]), 'null', '{}']) { data.setItem(tabs.storageKey, record); assert.deepEqual(tabs.createStore(() => data, scope).read(), []); } const store = tabs.createStore(() => data, () => ({id: '', user: '', role: ''})); store.write([dashboard]); assert.deepEqual(store.read(), []); }); test('port subpages normalize to one tab and corrupt entries are dropped', () => { const result = tabs.sanitizeTabs([null, {}, port, {...port, path: '/ports/16/advanced'}, dashboard], 'port_operator'); assert.deepEqual(result, [{key: 'port-16', path: '/ports/16/parameters', label: '포트 16 설정'}, dashboard]); }); test('storage denied by browser falls back to working in-memory tabs', () => { const store = tabs.createStore(() => { throw new Error('Storage denied'); }, () => ({id: 'login', user: 'operator', role: 'port_operator'})); store.write([dashboard, port]); assert.equal(store.read().length, 2); store.clear(); assert.deepEqual(store.read(), []); });