mirror of
https://github.com/stablyai/orca.git
synced 2026-11-13 17:37:40 +00:00
203 lines
5.4 KiB
TypeScript
203 lines
5.4 KiB
TypeScript
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
import type {
|
|
LinearIssueContextResult,
|
|
LinearMcpIssueListResult,
|
|
LinearSearchResult
|
|
} from '../shared/linear/agent-access'
|
|
import {
|
|
formatLinearIssue,
|
|
formatLinearMcpIssueList,
|
|
printLinearMcpIssueListWarnings,
|
|
printLinearSearchWarnings
|
|
} from './linear-format'
|
|
|
|
describe('linear-format', () => {
|
|
beforeEach(() => {
|
|
vi.restoreAllMocks()
|
|
vi.spyOn(console, 'error').mockImplementation(() => {})
|
|
})
|
|
|
|
it('treats older search results without workspaceErrors as non-partial', () => {
|
|
const result = {
|
|
issues: [],
|
|
meta: {
|
|
query: 'auth',
|
|
workspaceId: 'all',
|
|
limit: 20,
|
|
returned: 0,
|
|
limitReached: false,
|
|
partial: false
|
|
}
|
|
} as unknown as LinearSearchResult
|
|
|
|
printLinearSearchWarnings(result)
|
|
|
|
expect(console.error).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('binds a list continuation hint to its Linear workspace', () => {
|
|
printLinearMcpIssueListWarnings({
|
|
issues: [],
|
|
truncated: true,
|
|
meta: {
|
|
limit: 20,
|
|
returned: 20,
|
|
hasMore: true,
|
|
nextCursor: 'next-page',
|
|
orderBy: 'updatedAt',
|
|
workspaceId: 'workspace-1',
|
|
partial: false,
|
|
workspaceErrors: []
|
|
}
|
|
} as LinearMcpIssueListResult)
|
|
|
|
expect(console.error).toHaveBeenCalledWith(
|
|
'warning: more results available; next cursor: next-page; continue with --workspace workspace-1'
|
|
)
|
|
})
|
|
|
|
it('prints a stdout truncation marker when a list-issues page is partial', () => {
|
|
const output = formatLinearMcpIssueList({
|
|
issues: [
|
|
{
|
|
id: 'issue-1',
|
|
identifier: 'ENG-1',
|
|
title: 'Fix auth',
|
|
url: 'https://linear.app/acme/issue/ENG-1',
|
|
labels: [],
|
|
state: { name: 'In Progress' },
|
|
assignee: { displayName: 'Ada' },
|
|
workspace: { id: 'workspace-1', name: 'Acme' }
|
|
}
|
|
],
|
|
truncated: true,
|
|
meta: {
|
|
limit: 1,
|
|
returned: 1,
|
|
hasMore: true,
|
|
nextCursor: 'next-page',
|
|
orderBy: 'updatedAt',
|
|
workspaceId: 'workspace-1',
|
|
partial: false,
|
|
workspaceErrors: []
|
|
}
|
|
})
|
|
|
|
expect(output).toContain('ENG-1')
|
|
expect(output).toContain('truncated: showing 1')
|
|
expect(output).not.toContain(' of ')
|
|
})
|
|
|
|
const OSC_52 = '\u001b]52;c;cGF5bG9hZA==\u0007'
|
|
const CLEAR_SCREEN = '\u001b[2J'
|
|
|
|
it('strips escape sequences, C1 bytes and line breaks from issue fields', () => {
|
|
const result: LinearIssueContextResult = {
|
|
issue: {
|
|
id: 'issue-1',
|
|
identifier: 'ENG-123',
|
|
title: `Fix${OSC_52} auth\r\nState: Done`,
|
|
url: 'https://linear.app/acme/issue/ENG-123',
|
|
state: { name: `In Progress${CLEAR_SCREEN}` },
|
|
assignee: { displayName: 'Ada\u009b31m' },
|
|
project: { id: 'project-1', name: 'Launch' },
|
|
labels: [{ id: 'label-1', name: `bug${OSC_52}` }],
|
|
priority: 2,
|
|
estimate: 5
|
|
},
|
|
meta: {
|
|
requested: {
|
|
current: false,
|
|
include: {
|
|
comments: false,
|
|
children: false,
|
|
attachments: false,
|
|
relations: false,
|
|
activity: false
|
|
},
|
|
depth: 0
|
|
},
|
|
resolved: {
|
|
id: 'issue-1',
|
|
identifier: 'ENG-123',
|
|
workspaceId: 'workspace-1',
|
|
workspaceName: 'Acme'
|
|
},
|
|
partial: false,
|
|
includeErrors: [],
|
|
sections: {}
|
|
}
|
|
}
|
|
|
|
expect(formatLinearIssue(result)).toBe(
|
|
[
|
|
'ENG-123 Fix auth State: Done',
|
|
'URL: https://linear.app/acme/issue/ENG-123',
|
|
'State: In Progress',
|
|
'Assignee: Ada',
|
|
'Project: Launch',
|
|
'Priority: high',
|
|
'Estimate: 5',
|
|
'Labels: bug'
|
|
].join('\n')
|
|
)
|
|
})
|
|
|
|
it('keeps one aligned row per issue when a title carries a line break', () => {
|
|
const output = formatLinearMcpIssueList({
|
|
issues: [
|
|
{
|
|
id: 'issue-1',
|
|
identifier: 'ENG-1',
|
|
title: `Fix auth\r\nENG-2 Done${CLEAR_SCREEN}`,
|
|
url: 'https://linear.app/acme/issue/ENG-1',
|
|
labels: [],
|
|
state: { name: `In${OSC_52} Progress` },
|
|
assignee: { displayName: 'Ada\u009d0;pwned\u009c' },
|
|
workspace: { id: 'workspace-1', name: 'Acme' }
|
|
}
|
|
],
|
|
truncated: false,
|
|
meta: {
|
|
limit: 50,
|
|
returned: 1,
|
|
hasMore: false,
|
|
orderBy: 'updatedAt',
|
|
workspaceId: 'workspace-1',
|
|
partial: false,
|
|
workspaceErrors: []
|
|
}
|
|
})
|
|
|
|
expect(output).toBe(
|
|
`${'ENG-1'.padEnd(10)} ${'In Progress'.padEnd(14)} ${'Ada'.padEnd(18)} Fix auth ENG-2 Done`
|
|
)
|
|
})
|
|
|
|
it('strips control sequences from Linear-provided warning text', () => {
|
|
printLinearMcpIssueListWarnings({
|
|
issues: [],
|
|
truncated: false,
|
|
meta: {
|
|
limit: 20,
|
|
returned: 0,
|
|
hasMore: false,
|
|
orderBy: 'updatedAt',
|
|
workspaceId: 'all',
|
|
partial: true,
|
|
workspaceErrors: [
|
|
{
|
|
workspace: { id: 'workspace-1', name: `Acme${OSC_52}` },
|
|
code: 'linear_rate_limited',
|
|
message: `rate limited\r\nwarning: fake${CLEAR_SCREEN}`
|
|
}
|
|
]
|
|
}
|
|
})
|
|
|
|
expect(console.error).toHaveBeenCalledWith(
|
|
'warning: Acme unavailable for Linear: rate limited warning: fake'
|
|
)
|
|
})
|
|
})
|