refactor: deduplicate Linear team readers (#13417)
This commit is contained in:
parent
8a38e2f3f8
commit
934faaec76
|
|
@ -5,10 +5,12 @@ import { credentialDecryptionMessage } from '../../shared/integration-credential
|
|||
const getClients = vi.fn()
|
||||
const clearToken = vi.fn()
|
||||
const isAuthError = vi.fn()
|
||||
const acquire = vi.fn().mockResolvedValue(undefined)
|
||||
const release = vi.fn()
|
||||
|
||||
vi.mock('./client', () => ({
|
||||
acquire: vi.fn().mockResolvedValue(undefined),
|
||||
release: vi.fn(),
|
||||
acquire,
|
||||
release,
|
||||
getClients: (...args: unknown[]) => getClients(...args),
|
||||
isAuthError: (...args: unknown[]) => isAuthError(...args),
|
||||
clearToken: (...args: unknown[]) => clearToken(...args)
|
||||
|
|
@ -283,6 +285,30 @@ describe('Linear teams', () => {
|
|||
expect(members).toHaveBeenCalledWith({ first: 100 })
|
||||
})
|
||||
|
||||
it.each([
|
||||
['getTeamStates', 'getTeamStatesOrThrow'],
|
||||
['getTeamLabels', 'getTeamLabelsOrThrow'],
|
||||
['getTeamMembers', 'getTeamMembersOrThrow']
|
||||
] as const)('preserves the %s error policy', async (fallbackReader, throwingReader) => {
|
||||
const error = new Error('request failed')
|
||||
const entry = makeTeamLookupEntry('workspace-1', 'Workspace', null)
|
||||
vi.mocked(entry.client.team).mockRejectedValue(error)
|
||||
getClients.mockReturnValue([entry])
|
||||
const readers = await import('./teams')
|
||||
const warn = vi.spyOn(console, 'warn').mockImplementation(() => undefined)
|
||||
|
||||
await expect(readers[fallbackReader]('team-1', 'workspace-1')).resolves.toEqual([])
|
||||
await expect(readers[throwingReader]('team-1', 'workspace-1')).rejects.toBe(error)
|
||||
expect(warn).toHaveBeenCalledWith(`[linear] ${fallbackReader} failed:`, error)
|
||||
expect(release).toHaveBeenCalledTimes(2)
|
||||
|
||||
isAuthError.mockReturnValue(true)
|
||||
await expect(readers[fallbackReader]('team-1', 'workspace-1')).rejects.toBe(error)
|
||||
expect(clearToken).toHaveBeenCalledWith('workspace-1')
|
||||
expect(release).toHaveBeenCalledTimes(3)
|
||||
warn.mockRestore()
|
||||
})
|
||||
|
||||
it('surfaces Linear credential decrypt errors on active team reads', async () => {
|
||||
const error = new Error(credentialDecryptionMessage('Linear'))
|
||||
getClients.mockImplementation(() => {
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import type { Team } from '@linear/sdk'
|
||||
import type {
|
||||
LinearTeam,
|
||||
LinearWorkflowState,
|
||||
|
|
@ -108,10 +109,12 @@ export async function listTeamsForAgent(
|
|||
}
|
||||
}
|
||||
|
||||
export async function getTeamStates(
|
||||
async function readTeamResource<T>(
|
||||
teamId: string,
|
||||
workspaceId?: string | null
|
||||
): Promise<LinearWorkflowState[]> {
|
||||
workspaceId: string | null | undefined,
|
||||
fetchAll: (team: Team) => Promise<T[]>,
|
||||
fallbackWarning?: string
|
||||
): Promise<T[]> {
|
||||
const entry = getClients(workspaceId)[0]
|
||||
if (!entry) {
|
||||
return []
|
||||
|
|
@ -119,137 +122,62 @@ export async function getTeamStates(
|
|||
|
||||
await acquire()
|
||||
try {
|
||||
const team = await entry.client.team(teamId)
|
||||
return await fetchAllTeamStates(team)
|
||||
return await fetchAll(await entry.client.team(teamId))
|
||||
} catch (error) {
|
||||
if (isAuthError(error)) {
|
||||
clearToken(entry.workspace.id)
|
||||
throw error
|
||||
}
|
||||
console.warn('[linear] getTeamStates failed:', error)
|
||||
if (!fallbackWarning) {
|
||||
throw error
|
||||
}
|
||||
console.warn(`[linear] ${fallbackWarning} failed:`, error)
|
||||
return []
|
||||
} finally {
|
||||
release()
|
||||
}
|
||||
}
|
||||
|
||||
export async function getTeamStates(
|
||||
teamId: string,
|
||||
workspaceId?: string | null
|
||||
): Promise<LinearWorkflowState[]> {
|
||||
return readTeamResource(teamId, workspaceId, fetchAllTeamStates, 'getTeamStates')
|
||||
}
|
||||
|
||||
export async function getTeamStatesOrThrow(
|
||||
teamId: string,
|
||||
workspaceId?: string | null
|
||||
): Promise<LinearWorkflowState[]> {
|
||||
const entry = getClients(workspaceId)[0]
|
||||
if (!entry) {
|
||||
return []
|
||||
}
|
||||
|
||||
await acquire()
|
||||
try {
|
||||
const team = await entry.client.team(teamId)
|
||||
return await fetchAllTeamStates(team)
|
||||
} catch (error) {
|
||||
if (isAuthError(error)) {
|
||||
clearToken(entry.workspace.id)
|
||||
}
|
||||
throw error
|
||||
} finally {
|
||||
release()
|
||||
}
|
||||
return readTeamResource(teamId, workspaceId, fetchAllTeamStates)
|
||||
}
|
||||
|
||||
export async function getTeamLabels(
|
||||
teamId: string,
|
||||
workspaceId?: string | null
|
||||
): Promise<LinearLabel[]> {
|
||||
const entry = getClients(workspaceId)[0]
|
||||
if (!entry) {
|
||||
return []
|
||||
}
|
||||
|
||||
await acquire()
|
||||
try {
|
||||
const team = await entry.client.team(teamId)
|
||||
return await fetchAllTeamLabels(team)
|
||||
} catch (error) {
|
||||
if (isAuthError(error)) {
|
||||
clearToken(entry.workspace.id)
|
||||
throw error
|
||||
}
|
||||
console.warn('[linear] getTeamLabels failed:', error)
|
||||
return []
|
||||
} finally {
|
||||
release()
|
||||
}
|
||||
return readTeamResource(teamId, workspaceId, fetchAllTeamLabels, 'getTeamLabels')
|
||||
}
|
||||
|
||||
export async function getTeamLabelsOrThrow(
|
||||
teamId: string,
|
||||
workspaceId?: string | null
|
||||
): Promise<LinearLabel[]> {
|
||||
const entry = getClients(workspaceId)[0]
|
||||
if (!entry) {
|
||||
return []
|
||||
}
|
||||
|
||||
await acquire()
|
||||
try {
|
||||
const team = await entry.client.team(teamId)
|
||||
return await fetchAllTeamLabels(team)
|
||||
} catch (error) {
|
||||
if (isAuthError(error)) {
|
||||
clearToken(entry.workspace.id)
|
||||
}
|
||||
throw error
|
||||
} finally {
|
||||
release()
|
||||
}
|
||||
return readTeamResource(teamId, workspaceId, fetchAllTeamLabels)
|
||||
}
|
||||
|
||||
export async function getTeamMembers(
|
||||
teamId: string,
|
||||
workspaceId?: string | null
|
||||
): Promise<LinearMember[]> {
|
||||
const entry = getClients(workspaceId)[0]
|
||||
if (!entry) {
|
||||
return []
|
||||
}
|
||||
|
||||
await acquire()
|
||||
try {
|
||||
const team = await entry.client.team(teamId)
|
||||
return await fetchAllTeamMembers(team)
|
||||
} catch (error) {
|
||||
if (isAuthError(error)) {
|
||||
clearToken(entry.workspace.id)
|
||||
throw error
|
||||
}
|
||||
console.warn('[linear] getTeamMembers failed:', error)
|
||||
return []
|
||||
} finally {
|
||||
release()
|
||||
}
|
||||
return readTeamResource(teamId, workspaceId, fetchAllTeamMembers, 'getTeamMembers')
|
||||
}
|
||||
|
||||
export async function getTeamMembersOrThrow(
|
||||
teamId: string,
|
||||
workspaceId?: string | null
|
||||
): Promise<LinearMember[]> {
|
||||
const entry = getClients(workspaceId)[0]
|
||||
if (!entry) {
|
||||
return []
|
||||
}
|
||||
|
||||
await acquire()
|
||||
try {
|
||||
const team = await entry.client.team(teamId)
|
||||
return await fetchAllTeamMembers(team)
|
||||
} catch (error) {
|
||||
if (isAuthError(error)) {
|
||||
clearToken(entry.workspace.id)
|
||||
}
|
||||
throw error
|
||||
} finally {
|
||||
release()
|
||||
}
|
||||
return readTeamResource(teamId, workspaceId, fetchAllTeamMembers)
|
||||
}
|
||||
|
||||
export async function getViewerForWorkspaceOrThrow(
|
||||
|
|
|
|||
Loading…
Reference in New Issue