File size: 3,011 Bytes
39e315a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
import { afterEach, beforeEach, expect, mock, test } from 'bun:test'

// Mock the Anthropic-API-side before importing the module under test, so
// queryHaiku resolves into whatever the individual test wants (slow, failing,
// or successful). We preserve every other export from claude.js so unrelated
// transitive imports still work.
const haikuMock = mock()

beforeEach(async () => {
  haikuMock.mockReset()
  const actual = await import('../../services/api/claude.js')
  mock.module('../../services/api/claude.js', () => ({
    ...actual,
    queryHaiku: haikuMock,
  }))
})

afterEach(() => {
  mock.restore()
})

async function runApply(markdown = 'Hello world.', signal?: AbortSignal): Promise<string> {
  const nonce = `${Date.now()}-${Math.random()}`
  const { applyPromptToMarkdown } =
    await import(`./utils.js?ts=${nonce}`)
  const ctrl = new AbortController()
  return applyPromptToMarkdown(
    'summarize',
    markdown,
    signal ?? ctrl.signal,
    false,
    false,
  )
}

test('returns raw truncated markdown when queryHaiku throws', async () => {
  haikuMock.mockImplementation(async () => {
    throw new Error('MiniMax rejected the model name')
  })

  const output = await runApply('Gitlawb homepage content.')
  expect(output).toContain('[Secondary-model summarization unavailable')
  expect(output).toContain('Gitlawb homepage content.')
})

test('returns raw truncated markdown when queryHaiku simulates a timeout', async () => {
  // Simulating raceWithTimeout's rejection path directly — we can't actually
  // wait 45s in a test. The error shape matches what raceWithTimeout produces.
  haikuMock.mockImplementation(async () => {
    const err = new Error('Secondary-model summarization timed out after 45000ms')
    ;(err as NodeJS.ErrnoException).code = 'SECONDARY_MODEL_TIMEOUT'
    throw err
  })

  const output = await runApply('Slow provider content.')
  expect(output).toContain('[Secondary-model summarization unavailable')
  expect(output).toContain('Slow provider content.')
})

test('returns the model response when queryHaiku succeeds', async () => {
  haikuMock.mockImplementation(async () => ({
    message: {
      content: [{ type: 'text', text: 'This page is about GitLawb, an AI legal platform.' }],
    },
  }))

  const output = await runApply('some page content')
  expect(output).toBe('This page is about GitLawb, an AI legal platform.')
})

test('returns fallback when queryHaiku resolves with empty content', async () => {
  haikuMock.mockImplementation(async () => ({ message: { content: [] } }))

  const output = await runApply('some page content')
  expect(output).toContain('[Secondary-model summarization unavailable')
  expect(output).toContain('some page content')
})

test('propagates AbortError from the caller signal', async () => {
  const ctrl = new AbortController()
  haikuMock.mockImplementation(async () => {
    ctrl.abort()
    return new Promise(() => {})
  })

  await expect(runApply('content', ctrl.signal)).rejects.toThrow()
})