import assert from 'node:assert/strict'; import fs from 'node:fs'; import path from 'node:path'; import test from 'node:test'; import { normalizeRequestError } from '../../.test-dist/src/web/features/subscription/requestError.js'; const root = path.resolve(import.meta.dirname, '../..'); const page = fs.readFileSync(path.join(root, 'src/web/components/ClientOverviewPage.tsx'), 'utf8'); const app = fs.readFileSync(path.join(root, 'src/web/App.tsx'), 'utf8'); const feature = fs.readFileSync(path.join(root, 'src/web/features/subscription/SubscriptionFeature.tsx'), 'utf8'); const styles = fs.readFileSync(path.join(root, 'src/web/styles/features/subscription.css'), 'utf8'); const boundary = fs.readFileSync(path.join(root, 'src/web/features/subscription/index.ts'), 'utf8'); test('subscription feature is the sole always-mounted lifecycle and view owner', () => { assert.match(boundary, /SubscriptionDeleteDialog,[\s\S]*SubscriptionPanel,[\s\S]*SubscriptionToggle,[\s\S]*useSubscriptionFeature/); assert.match(page, /from '\.\.\/features\/subscription\/index\.js'/); assert.equal((page.match(/useSubscriptionFeature\(/g) || []).length, 1); assert.equal((page.match(/ { assert.match(app, /const revisionRef = useRef\(0\)/); assert.match(app, /const hasAcceptedSnapshotRef = useRef\(false\)/); assert.equal((app.match(/if \(!hasAcceptedSnapshotRef\.current \|\| snapshot\.revision > revisionRef\.current\)/g) || []).length, 2); assert.equal((app.match(/revisionRef\.current = snapshot\.revision/g) || []).length, 2); assert.doesNotMatch(app, /if \(state\) revisionRef\.current = state\.revision/); assert.match(app, /api\.profiles\.add\(label, url, revisionRef\.current\)/); assert.match(app, /api\.profiles\.rename\(profileId, label, revisionRef\.current\)/); assert.match(app, /api\.profiles\.refresh\(profileId, revisionRef\.current\)/); assert.match(app, /api\.profiles\.forget\(profileId, mode, revisionRef\.current\)/); assert.match(page, /profiles,[\s\S]*onAdd: onAddProfile,[\s\S]*onRename: onRenameProfile,[\s\S]*onRefresh: onRefreshProfile,[\s\S]*onForget: onForgetProfile/); assert.doesNotMatch(feature, /from ['"][^'"]*\/api\/|\bapi\./); assert.doesNotMatch(feature, / { assert.match(feature, /isSubscriptionUrlValid\(normalizedUrl\)/); assert.doesNotMatch(feature, /validateSubscription\(|AbortController|setTimeout\(async/); assert.match(feature, /async function refresh\(profileId: string\)/); assert.match(feature, /Math\.max\(900, Math\.ceil\(elapsed \/ 900\) \* 900\)/); assert.match(feature, /previousProfileCountRef\.current === 0 && profiles\.length > 0[\s\S]*setOpen\(true\)[\s\S]*setExpanded/); assert.match(feature, /previousProfileCountRef\.current > 0\) setOpen\(false\)/); assert.match(feature, /if \(!adding \|\| \(profiles\.length > 0 && !open\)\) return undefined/); assert.match(feature, /if \(deleteIdRef\.current\) return;[\s\S]*toggleRef\.current\?\.focus\(\)/); assert.match(feature, /const invoker = addInvokerRef\.current[\s\S]*if \(invoker\) requestAnimationFrame\(\(\) => invoker\.focus\(\)\)/); assert.match(feature, /showAdd: \(\) => \{[\s\S]*addInvokerRef\.current = document\.activeElement instanceof HTMLElement/); assert.match(feature, /function cancelRename\(\)[\s\S]*client-profile-menu-\$\{profileId\}[\s\S]*\.focus\(\)/); assert.match(feature, /id=\{`client-profile-menu-\$\{profile\.id\}`\}/); }); test('validation rejection parser preserves structured errors and normalizes non-objects', () => { const retry = () => true; const structured = Object.assign(new Error('provider unavailable'), { name: 'HarborApiError', context: 'subscription', correlationId: 'correlation-1', retryable: true, retry, }); assert.deepEqual(normalizeRequestError(structured), { name: 'HarborApiError', context: 'subscription', message: 'provider unavailable', correlationId: 'correlation-1', retryable: true, retry, }); assert.deepEqual(normalizeRequestError(null), { name: undefined, context: undefined, message: 'Ссылка подписки недействительна.', correlationId: undefined, retryable: false, retry: null, }); assert.equal(normalizeRequestError('provider rejected').message, 'Ссылка подписки недействительна.'); }); test('feature keeps exact slots, truthy closes and subscription DOM order', () => { assert.match(feature, /if \(!await onAdd\(normalizedLabel, normalizedUrl\)\) return;[\s\S]*resetAdd\(\)/); assert.match(feature, /if \(!await onForget\(deleteProfile\.id, deleteStopsVpn \? 'stop-and-delete' : 'delete'\)\) return;[\s\S]*setDeleteId\(''\)/); assert.match(feature, /client-profiles-operation[\s\S]*client-profiles-current[\s\S]*\{statusSlot\}[\s\S]*client-profile-list/); assert.match(feature, /const drawerOpen = feature\.open && feature\.profiles\.length > 0/); assert.match(feature, /feature\.profiles\.length === 0 &&
[\s\S]*InlineError[\s\S]*renderServerPicker=[\s\S]*]*context="subscription"/); assert.match(page, / { assert.match(feature, /feature\.profiles\.map\(\(profile\) =>