Files
orca/src/shared/github-api-availability.test.ts
T
Brennan Benson ef03a50b1d fix(github): attribute GitHub API outages instead of blank/"failed" states (#9106)
* fix(github): attribute GitHub API outages instead of blank/"failed" states

When GitHub's API is unreachable (5xx outage, network, or rate limit), Orca
showed no PR data with no explanation, so it read as an Orca bug rather than a
GitHub-side problem.

- Add a shared classifier (classifyGitHubUnavailable) reused by the main
  process and the renderer so every surface attributes an outage identically.
  A live outage returns HTTP 5xx, which the PR-refresh classifier previously
  had no branch for (fell through to the un-attributed "refresh failed").
- Right-sidebar Checks panel: show GitHub-attributed copy in the error
  empty-state, plus an inline banner over stale cached PR data so an outage
  doesn't look like a normal (silently out-of-date) panel.
- Tasks/PR-list page: replace the vague "N of M projects failed to load" with
  a GitHub-attributed banner when the failure is a reachability problem.

Copy names GitHub as the source and reassures it isn't an Orca problem, with no
status-page link. Stays GitHub-scoped so GitLab/other providers aren't
mislabeled.

* fix(github): keep outage attribution accurate

* fix(github): preserve outage attribution edge cases

* fix(github): preserve Tasks outage attribution

* fix(github): avoid false outage attribution

* fix(runtime): tolerate absent browser certificate state

* fix(ui): preserve exhaustive optional state handling

* fix(github): preserve outage attribution for combined queries

* fix(github): preserve runtime failure attribution

* chore: restore unrelated UI files to main (out of scope)

native-chat-session-option-labels.ts and skill-freshness-group.tsx switch
tweaks were unrelated to GitHub API outage attribution — they fix pre-existing
switch-exhaustiveness lint on main, which this PR's CI (oxlint) doesn't gate on.
Restore them to origin/main so this PR's diff stays focused; the exhaustiveness
cleanup belongs in its own change. (sync-runtime-graph.ts is already identical
to main, so no diff there to revert.)

* fix(github): drop Orca self-reference from outage copy

* fix(github): drop em-dashes from outage copy
2026-07-17 15:16:07 -07:00

68 lines
2.9 KiB
TypeScript

import { describe, expect, it } from 'vitest'
import { classifyGitHubUnavailable, isGitHubUnavailableError } from './github-api-availability'
describe('classifyGitHubUnavailable', () => {
it('classifies HTTP 5xx outages as server_error', () => {
expect(classifyGitHubUnavailable('HTTP 503: Service Unavailable')).toBe('server_error')
expect(classifyGitHubUnavailable('gh: Command failed: HTTP 502 Bad Gateway')).toBe(
'server_error'
)
expect(classifyGitHubUnavailable('GitHub API error: 500 Internal Server Error')).toBe(
'server_error'
)
expect(classifyGitHubUnavailable('The service is temporarily unavailable')).toBe('server_error')
})
it('classifies transport failures as network', () => {
for (const message of [
'request to https://api.github.com failed, reason: getaddrinfo ENOTFOUND api.github.com',
'error connecting to api.github.com\ncheck your internet connection or GitHub status',
'dial tcp: lookup api.github.com: no such host',
'connect ETIMEDOUT 140.82.112.5:443',
'TimeoutError: request aborted',
'NetworkError when attempting to fetch resource',
'network unavailable',
'fetch failed',
'socket hang up',
'could not resolve host: api.github.com',
'connection refused'
]) {
expect(classifyGitHubUnavailable(message)).toBe('network')
}
})
it('classifies rate limiting as rate_limited (even when it carries HTTP 403)', () => {
expect(classifyGitHubUnavailable('HTTP 403: API rate limit exceeded')).toBe('rate_limited')
expect(classifyGitHubUnavailable('You have exceeded a secondary rate limit')).toBe(
'rate_limited'
)
expect(classifyGitHubUnavailable('HTTP 429 Too Many Requests')).toBe('rate_limited')
})
it('returns null for non-reachability failures', () => {
expect(classifyGitHubUnavailable('HTTP 403: Resource not accessible by integration')).toBeNull()
expect(classifyGitHubUnavailable('HTTP 404: Not Found')).toBeNull()
expect(classifyGitHubUnavailable('could not resolve to a Repository with the name')).toBeNull()
expect(
classifyGitHubUnavailable(
"GraphQL: Could not resolve to a Repository with the name 'network'."
)
).toBeNull()
expect(classifyGitHubUnavailable('gh auth login required')).toBeNull()
expect(classifyGitHubUnavailable('')).toBeNull()
})
it('does not misread unrelated 3-digit numbers as a server outage', () => {
expect(classifyGitHubUnavailable('found 512 pull requests')).toBeNull()
})
})
describe('isGitHubUnavailableError', () => {
it('detects reachability failures from Error objects and strings', () => {
expect(isGitHubUnavailableError(new Error('HTTP 503: Service Unavailable'))).toBe(true)
expect(isGitHubUnavailableError('fetch failed')).toBe(true)
expect(isGitHubUnavailableError(new Error('HTTP 404: Not Found'))).toBe(false)
expect(isGitHubUnavailableError(null)).toBe(false)
})
})