Files
MarketingTool/apps/web/smoke-test.html
T

63 lines
11 KiB
HTML

<!doctype html>
<meta charset="utf-8">
<title>ProspectOS smoke test</title>
<style>body{font:16px system-ui;padding:2rem;background:#f7f8fb;color:#172033}li{margin:.5rem 0}.pass{color:#16845b}.fail{color:#b84d55}</style>
<h1>ProspectOS static smoke test</h1><p id="summary">Running…</p><ul id="checks"></ul>
<iframe id="app" src="index.html" hidden></iframe>
<script>
const frame=document.querySelector('#app');
frame.onload=async()=>{const d=frame.contentDocument;const js=await fetch('app.js').then(r=>r.text());const checks=[
['Login screen',()=>!!d.querySelector('#loginScreen')],
['Email/password login fields',()=>!!d.querySelector('#loginEmail')&&!!d.querySelector('#loginPassword')],
['Dashboard starts protected',()=>d.querySelector('#dashboardShell').hidden],
['Logout and user display',()=>!!d.querySelector('#logoutBtn')&&!!d.querySelector('#userIdentity')],
['Explorer pagination and filters',()=>!!d.querySelector('#pageSize')&&!!d.querySelector('#nextPageBtn')&&!!d.querySelector('#websiteClassFilter')&&!!d.querySelector('#pipelineFilter')],
['Detail panel contract',()=>!!d.querySelector('#detailPanel')&&js.includes('/api/v1/businesses/${encodeURIComponent(id)}')&&js.includes('contacts')&&js.includes('evidence_timeline')&&js.includes('pipeline_stage')],
['Manual detail controls',()=>js.includes('contactForm')&&js.includes('noteForm')&&js.includes('pipelineForm')&&js.includes('verifyBtn')],
['All API requests include cookies',()=>[...js.matchAll(/fetch\([^;]+/g)].every(m=>m[0].includes("credentials:'include'"))],
['Detail writes use authenticated request helper',()=>['/contacts','/notes','/pipeline','/verify'].every(path=>js.includes(path)&&js.includes('jsonRequest'))],
['Validation and error states',()=>js.includes('Email is required.')&&js.includes('Note cannot be empty.')&&js.includes('detail-error')&&js.includes('role="alert"')],
['No hardcoded credentials or demo fallback',()=>!js.includes('demoProspects')&&!/password.{0,30}['\"][^'\"]+['\"]/.test(js)],
['Safety copy and blocked outreach preserved',()=>js.includes('disabled-action')&&js.includes('Suppressed records cannot be contacted.')&&js.includes('Review this prospect before outreach is available.')],
['No outreach/send controls',()=>!Array.from(d.querySelectorAll('button')).some(x=>/outreach|send/i.test(x.textContent)&&!x.disabled)]
,['Jobs navigation and monitor',()=>!!d.querySelector('[data-nav="jobs"]')&&!!d.querySelector('#jobs')&&!!d.querySelector('#jobsList')]
,['Job status counts and controls',()=>['jobCountQueued','jobCountRunning','jobCountSucceeded','jobCountFailed','jobCountCancelled','startDemoJobBtn','jobsRefreshBtn'].every(id=>!!d.querySelector('#'+id))]
,['Authenticated jobs API contract',()=>js.includes("/api/v1/jobs")&&js.includes('jobsRequest')&&js.includes('/cancel')&&js.includes('/retry')]
,['Job detail timeline and structured states',()=>!!d.querySelector('#jobDetailPanel')&&js.includes('event_timeline')&&js.includes('structured_error')&&js.includes('progress')]
,['Sources navigation and safety boundary',()=>!!d.querySelector('[data-nav="sources"]')&&!!d.querySelector('#sources')&&js.includes('No live source is enabled')]
,['Source registry status and governance fields',()=>!!d.querySelector('#sourcesList')&&['owner','terms','rate_limit','health'].every(x=>js.includes(x))&&js.includes('/api/v1/sources')]
,['Source configuration and controls use authenticated helper',()=>!!d.querySelector('#sourceForm')&&!!d.querySelector('#sourceType')&&js.includes('saveSource')&&js.includes('/test')&&js.includes('method:\'PATCH\'')&&js.includes('jsonRequest')]
,['Discovery dry-run and run controls',()=>!!d.querySelector('#discoveryForm')&&!!d.querySelector('#discoveryDryRunBtn')&&!!d.querySelector('#discoveryRunBtn')&&js.includes('/api/v1/sources/discovery')&&js.includes('dry_run')]
,['Recent source records and error/loading states',()=>!!d.querySelector('#sourceRecordsList')&&js.includes('source-record-table')&&js.includes('Loading source registry')&&js.includes('Unable to load sources')]
,['No live source enabled copy is explicit',()=>d.querySelector('#sources')?.textContent.includes('No live source is enabled')&&!js.includes('demoSources')]
,['Deduplication review UI contract',()=>!!d.querySelector('#mergeDialog')&&js.includes('/matches')&&js.includes('review-required')&&js.includes('confidence')&&js.includes('reasons')]
,['Merge actions are explicit and reversible',()=>js.includes('/merge-history')&&js.includes('/reverse')&&js.includes('This action is reversible')&&js.includes('Confirm merge')&&!js.includes('autoMerge')]
,['Deduplication loading and errors',()=>js.includes('Loading match suggestions')&&js.includes('Unable to load match suggestions')&&js.includes('merge-history')&&js.includes('Unable to load merge history')]
,['Domain intelligence smoke marker and controls',()=>!!d.querySelector('[data-smoke="domain-intelligence"]')&&js.includes('/domains/check')&&js.includes('/domain-candidates')&&js.includes('runDomainCheckBtn')]
,['DNS capability and uncertainty labels',()=>['DNS checks','A','AAAA','MX','NS','TXT','NXDOMAIN','Unknown','Error','Cache'].every(x=>js.includes(x))]
,['Candidate confidence, reasons, and safe availability',()=>js.includes('confidence')&&js.includes('reasons')&&js.includes('check-availability')&&js.includes('Not configured')&&js.includes('No ownership conclusion')&&!js.includes('Domain is available')]
,['Domain loading, error, tenant, and auth states',()=>js.includes('Loading DNS results')&&js.includes('Unable to load DNS results')&&js.includes('Tenant/workspace access denied')&&js.includes("credentials:'include'")]
,['Website scan smoke marker and authenticated controls',()=>!!d.querySelector('[data-smoke="website-scan"]')&&!!d.querySelector('#scanWebsiteBtn')&&!!d.querySelector('#refreshWebsiteScanBtn')&&js.includes('/websites/scan')&&js.includes("method:'POST'")&&js.includes("credentials:'include'")]
,['Website scan fields and conservative states',()=>['Scan status','HTTP status','Final URL','Redirect chain','Classification','Title','Meta description','Viewport','HTTPS / TLS','Load time','Response size','Contact signals','Checked','Cache','Blocked','Unknown','Error'].every(x=>js.includes(x))]
,['Website scan loading and error states',()=>js.includes('Loading website scan')&&js.includes('Unable to load website scan')&&js.includes('Website scan failed')&&js.includes('role="alert"')]
,['Website scan avoids browser execution claims',()=>!js.includes('screenshot')&&!js.includes('browser execution')&&!js.includes('page screenshot')]
,['Website scan responsive styles',()=>js.includes('website-scan-panel')&&js.includes('website-scan-grid')&&js.includes('@media')]
,['Public contact extraction smoke marker and controls',()=>!!d.querySelector('[data-smoke="contact-extraction"]')&&!!d.querySelector('#extractContactsBtn')&&!!d.querySelector('#refreshContactExtractionBtn')&&js.includes('/contacts/extract')&&js.includes('jsonRequest')]
,['Extracted contact evidence fields',()=>['Extracted contacts','Type','Email class','Validation','confidence','Source URL','Suppression'].every(x=>js.includes(x))]
,['Contact extraction safety and states',()=>js.includes('approved public business pages only')&&js.includes('SMTP')&&js.includes('outreach')&&js.includes('Loading public contacts')&&js.includes('No public contacts found')&&js.includes('Contact extraction failed')]
,['Score breakdown and recalculation contract',()=>!!d.querySelector('[data-smoke="score-breakdown"]')&&js.includes('Score breakdown')&&js.includes('Total score')&&js.includes('Priority')&&js.includes('Eligibility')&&js.includes('Score version')&&js.includes('/score/recalculate')&&js.includes('recalculateScoreBtn')]
,['Score rules configuration and permission affordance',()=>!!d.querySelector('[data-smoke="score-rules"]')&&!!d.querySelector('#scoreRules')&&js.includes('/api/v1/score-rules')&&js.includes('enabled')&&js.includes('points')&&js.includes('Read-only for this role')&&js.includes('canEditScores')]
,['Score distribution summary and safe states',()=>!!d.querySelector('[data-smoke="score-distribution"]')&&js.includes('/scoring/summary')&&js.includes('Loading score distribution')&&js.includes('Unable to load score distribution')&&js.includes('tenant-scoped')&&js.includes('do not override suppression')]
,['Score responsive styles',()=>js.includes('score-breakdown-summary')&&js.includes('score-config-row')&&js.includes('distribution-track')&&js.includes('@media')]
,['Phase 11 saved views and review queue controls',()=>!!d.querySelector('[data-smoke="saved-views"]')&&!!d.querySelector('[data-smoke="review-queue"]')&&!!d.querySelector('#savedFilterForm')&&!!d.querySelector('#savedFilterSelect')&&!!d.querySelector('#bulkVerifyBtn')&&!!d.querySelector('#bulkRejectBtn')]
,['Phase 11 authenticated saved-view and bulk-review contracts',()=>js.includes('/api/v1/saved-filters')&&js.includes('/api/v1/review-queue')&&js.includes('/api/v1/businesses/bulk-review')&&js.includes('jsonRequest')&&js.includes('window.confirm')]
,['Phase 11 bounded selection and safe labels',()=>js.includes('slice(0,100)')&&js.includes('selected visible')&&js.includes('no outreach will be sent')&&d.querySelector('#reviewQueueCount')]
,['Phase 11 dashboard cards link to filters and responsive styles',()=>d.querySelectorAll('[data-dashboard-filter]').length>=5&&js.includes('applyFilters')&&js.includes('metricSuppressed')&&js.includes('saved-view-controls')&&js.includes('@media')]
,['Phase 12 CRM pipeline board/list and explicit transitions',()=>!!d.querySelector('[data-smoke="crm-pipeline"]')&&!!d.querySelector('#pipelineBoard')&&!!d.querySelector('#pipelineViewToggle')&&js.includes('/api/v1/pipeline-entries')&&js.includes('Stage transition saved')&&js.includes('outreach:false')&&js.includes('window.confirm')]
,['Phase 12 interaction timeline and metadata form',()=>!!d.querySelector('[data-smoke="crm-interactions"]')&&!!d.querySelector('#interactionForm')&&!!d.querySelector('#interactionState')&&js.includes('/api/v1/interactions?business_id=')&&js.includes('follow_up_at')&&js.includes('outcome')&&js.includes('No message was sent')]
,['Phase 12 reports panels and API states',()=>!!d.querySelector('[data-smoke="crm-reports"]')&&['pipelineReport','outcomesReport','activityReport'].every(id=>!!d.querySelector('#'+id))&&['/api/v1/reports/pipeline','/api/v1/reports/outcomes','/api/v1/reports/activity'].every(path=>js.includes(path))&&js.includes('No')&&js.includes('Unable to load')]
,['Phase 12 suppression center controls and safety',()=>!!d.querySelector('[data-smoke="suppression-center"]')&&!!d.querySelector('#suppressionForm')&&!!d.querySelector('#bulkReviewSuppressionsBtn')&&js.includes('/api/v1/suppressions')&&js.includes('data-remove-suppression')&&js.includes('Suppression always wins')&&js.includes('do-not-contact')]
,['Phase 12 CRM responsive styles and authenticated requests',()=>js.includes('credentials:\'include\'')&&js.includes('crm-two-col')&&js.includes('reports-grid')&&js.includes('pipeline-board')&&js.includes('@media')]
];let passed=0;document.querySelector('#checks').innerHTML=checks.map(([name,test])=>{const ok=test();if(ok)passed++;return `<li class="${ok?'pass':'fail'}">${ok?'PASS':'FAIL'}${name}</li>`}).join('');document.querySelector('#summary').textContent=`${passed}/${checks.length} checks passed`;};
</script>