File size: 3,817 Bytes
d0878a6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
import {
  extractAssertions,
  buildReferenceSearchQuery,
  buildWebSearchUrl,
  buildPerplexityUrl,
  truncateQuery,
  formatReferenceSnippet,
  wrapReferencesForAdvisorContext,
  getWebSearchEngine,
  setWebSearchEngine,
  SEARCH_QUERY_MAX_CHARS,
  readClipboardText,
} from './referenceSearch';

describe('reference search query builder', () => {
  test('extracts claim-like sentences from advisor markdown', () => {
    const text = `

## Guidance

You **must** enable MFA on every privileged account.

Teams should adopt a NIST CSF 2.0 program with clear ownership.

Hello there.

`;
    const claims = extractAssertions(text);
    expect(claims.some((c) => /MFA/i.test(c))).toBe(true);
    expect(claims.some((c) => /NIST/i.test(c))).toBe(true);
    expect(claims.join(' ')).not.toMatch(/Hello there/i);
  });

  test('builds a citation-oriented prompt with user context and stays under URL limits', () => {
    const query = buildReferenceSearchQuery({
      advisorName: 'Compliance Officer',
      userQuestion: 'How should we handle PCI DSS logging?',
      advisorText: 'PCI DSS requires retaining audit logs for at least one year. You must protect log integrity with WORM or equivalent controls.',
    });
    expect(query).toMatch(/primary sources, standards, and reputable citations/i);
    expect(query).toMatch(/PCI DSS/);
    expect(query).toMatch(/User question for context/);
    expect(query.length).toBeLessThanOrEqual(SEARCH_QUERY_MAX_CHARS);
  });

  test('truncates long queries on a word boundary', () => {
    const long = 'word '.repeat(800);
    const out = truncateQuery(long, 80);
    expect(out.length).toBeLessThanOrEqual(81);
    expect(out.endsWith('…')).toBe(true);
    expect(out).not.toMatch(/wo…$/);
  });

  test('builds Perplexity and web-search URLs with the encoded query', () => {
    const q = 'Find primary sources: MFA is required';
    expect(buildPerplexityUrl(q)).toBe(`https://www.perplexity.ai/?q=${encodeURIComponent(q)}`);
    expect(buildWebSearchUrl(q, 'ddg')).toBe(`https://duckduckgo.com/?q=${encodeURIComponent(q)}`);
    expect(buildWebSearchUrl(q, 'google')).toContain('google.com/search?q=');
  });

  test('persists the web search engine choice', () => {
    const store = {};
    const storage = {
      getItem: (k) => store[k] ?? null,
      setItem: (k, v) => { store[k] = v; },
    };
    expect(getWebSearchEngine(storage).id).toBe('google');
    setWebSearchEngine('brave', storage);
    expect(getWebSearchEngine(storage).id).toBe('brave');
  });

  test('formats clipboard snippets for chat context', () => {
    expect(formatReferenceSnippet('  NIST CSF 2.0  ')).toBe(
      'Here are references I found:\n\nNIST CSF 2.0'
    );
    expect(wrapReferencesForAdvisorContext('NIST CSF 2.0')).toMatch(/User-provided references/);
  });
});

describe('clipboard reader', () => {
  const original = navigator.clipboard;

  afterEach(() => {
    Object.defineProperty(navigator, 'clipboard', {
      configurable: true,
      value: original,
    });
  });

  test('returns clipboard text on a user-gesture read', async () => {
    Object.defineProperty(navigator, 'clipboard', {
      configurable: true,
      value: { readText: jest.fn().mockResolvedValue('copied citation') },
    });
    await expect(readClipboardText()).resolves.toEqual({
      text: 'copied citation',
      via: 'clipboard',
    });
  });

  test('maps permission denial to a denied error code', async () => {
    Object.defineProperty(navigator, 'clipboard', {
      configurable: true,
      value: { readText: jest.fn().mockRejectedValue(new Error('NotAllowedError')) },
    });
    await expect(readClipboardText()).rejects.toMatchObject({ code: 'denied' });
  });
});