66 lines
5.9 KiB
TypeScript
66 lines
5.9 KiB
TypeScript
// @vitest-environment jsdom
|
||
import { act } from 'react';
|
||
import { createRoot } from 'react-dom/client';
|
||
import { afterEach, expect, it, vi } from 'vitest';
|
||
import { read, utils } from 'xlsx';
|
||
import { FloatingResultsWindow } from '../../src/features/linked-results/FloatingResultsWindow';
|
||
import { resultsFindingContext } from '../../src/features/linked-results/results-excel-content';
|
||
import { buildResultsWindowExcel } from '../../src/exports/results-window-excel';
|
||
import { makeSheet } from '../../src/importers/sheet-factory';
|
||
import { downloadBrowserBlob } from '../../src/platform/browser/browser-download-adapter';
|
||
vi.mock('../../src/platform/browser/browser-download-adapter', () => ({ downloadBrowserBlob: vi.fn() }));
|
||
vi.mock('../../src/app/AppProvider', () => ({ useOptionalApp: () => undefined }));
|
||
afterEach(() => { vi.clearAllMocks(); vi.unstubAllGlobals(); delete document.documentElement.dataset.theme; });
|
||
const description = 'Checks certificate templates allowing a subjectAltName in the CSR.';
|
||
const result = 'Found 17 published templates on which unprivileged users can write properties.';
|
||
const impact = 'Controlling templates allows issuance of an arbitrary certificate. References Original reference';
|
||
const finding = makeSheet('Synthetic indicator', [['Name','Description','Result Message','Number of Results','Likelihood of Compromise','Result'],
|
||
['Certificate templates',description,result,'17',impact,'Failed']], '', 'synthetic.csv').analysis.records[0];
|
||
const expected = [['Description','Description',description],['Problem','Result',result],['Problem','Affected','17'],['Problem','Impact',impact]];
|
||
async function workbook(blob: Blob) {
|
||
const bytes = await new Promise<ArrayBuffer>((resolve,reject) => { const reader = new FileReader(); reader.onload = () => resolve(reader.result as ArrayBuffer); reader.onerror = reject; reader.readAsArrayBuffer(blob); });
|
||
return read(bytes,{type:'array'});
|
||
}
|
||
it.each(['light','dark'])('includes the complete original finding context in actual all and selected downloads (%s)', async theme => {
|
||
vi.stubGlobal('IS_REACT_ACT_ENVIRONMENT',true);vi.stubGlobal('ResizeObserver',class { observe() {} disconnect() {} });
|
||
document.documentElement.dataset.theme=theme;
|
||
const sheets = [makeSheet('Objects',[['Object'],['DUMMY-01'],['DUMMY-02']],'','synthetic.csv'),makeSheet('Second table',[['Other'],['DUMMY-03']],'','synthetic.csv')];
|
||
const before=JSON.stringify({finding,sheets});const host=document.createElement('div');document.body.append(host);const root=createRoot(host);
|
||
try {
|
||
await act(async () => root.render(<FloatingResultsWindow id="context" title="Results – Certificate templates" finding={finding} toolId="purple-knight" outcome="fail" sheets={sheets} color="" zIndex={1} offset={0} search="" onActivate={()=>{}} onClose={()=>{}}/>));
|
||
await act(async () => host.querySelector<HTMLButtonElement>('[aria-label="Export entire Results to Excel"]')!.click());
|
||
const all=await workbook(vi.mocked(downloadBrowserBlob).mock.calls.at(-1)![0]);
|
||
expect(all.SheetNames.slice(0,2)).toEqual(['Results','Results 02']);
|
||
expect(utils.sheet_to_json(all.Sheets['Finding context'],{header:1}).slice(2)).toEqual(expected);
|
||
expect(all.Sheets['Finding context'].A1.v).toBe('Certificate templates');
|
||
await act(async () => host.querySelector<HTMLInputElement>('input[type="checkbox"]')!.click());
|
||
await act(async () => host.querySelector<HTMLButtonElement>('[aria-label="Export selected tables to Excel"]')!.click());
|
||
const subset=await workbook(vi.mocked(downloadBrowserBlob).mock.calls.at(-1)![0]);
|
||
expect(subset.SheetNames).toHaveLength(3);expect(subset.SheetNames[1]).toBe('Finding context');
|
||
expect(utils.sheet_to_json(subset.Sheets['Finding context'],{header:1}).slice(2)).toEqual(expected);
|
||
expect(subset.SheetNames).not.toContain(all.SheetNames[1]);
|
||
expect(JSON.stringify({finding,sheets})).toBe(before);
|
||
} finally {await act(async()=>root.unmount());host.remove();}
|
||
});
|
||
it('preserves long descriptions completely and handles missing context without inventing numbers',()=>{
|
||
const long='Long description 🧪 '.repeat(2500);const copy={...finding,fields:finding.fields.map(field=>field.label==='Description'?{...field,value:long}:field)};
|
||
const context=resultsFindingContext(copy,'purple-knight',[],'fail');
|
||
const wb=read(buildResultsWindowExcel({title:'Text only',text:'Result',tables:[],sourceSections:[],findingContext:context}),{type:'array'});
|
||
expect(wb.SheetNames[0]).toBe('Results');
|
||
const parts=utils.sheet_to_json<string[]>(wb.Sheets['Long text'],{header:1}).slice(2);
|
||
expect(parts.filter(row=>row[0]==='Finding context'&&row[1]==='3'&&row[2]==='3').map(row=>row[4]).join('')).toBe(long);
|
||
expect(resultsFindingContext(undefined)).toEqual([]);
|
||
const generic={...finding,fields:[{label:'Description',value:'Original',index:0},{label:'Result',value:'Failed',index:1}]};
|
||
expect(resultsFindingContext(generic,'unknown')).toEqual([{section:'Description',label:'Description',value:'Original'}]);
|
||
});
|
||
it('retains the existing GPO problem comparison and backup scope without re-evaluation',()=>{
|
||
const policy={...finding,fields:[{label:'Description',value:'Original policy explanation',index:0},{label:'Status',value:'Fail',index:1}],
|
||
policyBaselineComparisons:[{baseline:'Synthetic baseline',status:'Fail',summary:'Stored comparison',context:'Backup only',scopeNotice:'Not effective host state',explanation:'Source',remediation:'Original guidance',
|
||
recommendations:[{template:'Synthetic',settingId:'s1',comparison:'different',expected:'Disabled',additional:[],missing:[]}]}]};
|
||
const before=JSON.stringify(policy);const context=resultsFindingContext(policy,'group-policy',[],'fail');
|
||
expect(context).toContainEqual({section:'Description',label:'Description',value:'Original policy explanation'});
|
||
expect(context).toContainEqual({section:'Problem',label:'Expected',value:'Disabled'});
|
||
expect(context.some(row=>row.label==='Affected')).toBe(false);
|
||
expect(JSON.stringify(policy)).toBe(before);
|
||
});
|