Files
orca/src/renderer/src/lib/github-links.test.ts
T
Jinjing 606e319fd1 Normalize GitHub work item type from URL to prevent issue/PR mismatch
- Resolve true work item identity (issue vs. PR) using the URL path to
  override stale or incorrect cached payload types.
- Prevent invalid PR start point resolution when launching an issue
  misclassified as a PR.
- Validate and reject mismatched URL types in the worktree metadata
  dialog fields to avoid incorrect associations.
2026-07-04 14:56:54 -07:00

184 lines
6.8 KiB
TypeScript

import { describe, expect, it } from 'vitest'
import {
buildGitHubRepoUrl,
normalizeGitHubLinkQuery,
parseGitHubIssueOrPRLink,
parseGitHubIssueOrPRNumber
} from './github-links'
import { WORK_ITEM_LINK_QUERY_MAX_BYTES } from './work-item-link-query-bounds'
describe('buildGitHubRepoUrl', () => {
it('builds a GitHub repository URL from an owner/repo slug', () => {
expect(buildGitHubRepoUrl({ owner: 'stablyai', repo: 'orca' })).toBe(
'https://github.com/stablyai/orca'
)
})
it('encodes path segments', () => {
expect(buildGitHubRepoUrl({ owner: 'stably ai', repo: 'orca/tools' })).toBe(
'https://github.com/stably%20ai/orca%2Ftools'
)
})
})
describe('parseGitHubIssueOrPRNumber', () => {
it('parses plain issue numbers and GitHub pull request URLs', () => {
expect(parseGitHubIssueOrPRNumber('42')).toBe(42)
expect(parseGitHubIssueOrPRNumber('#42')).toBe(42)
expect(parseGitHubIssueOrPRNumber('https://github.com/stablyai/orca/pull/123')).toBe(123)
expect(parseGitHubIssueOrPRNumber('https://github.com/stablyai/orca/issues/923')).toBe(923)
expect(parseGitHubIssueOrPRNumber('https://github.my-company.net/MyOrg/my_repo/pull/395')).toBe(
395
)
})
it('parses GitHub item URLs with trailing page segments', () => {
expect(parseGitHubIssueOrPRNumber('https://github.com/o/r/pull/1965/changes')).toBe(1965)
expect(parseGitHubIssueOrPRNumber('https://github.com/o/r/pull/1965/files')).toBe(1965)
expect(parseGitHubIssueOrPRNumber('https://github.com/o/r/pull/1965/commits')).toBe(1965)
expect(parseGitHubIssueOrPRNumber('https://github.com/o/r/issues/923/comments')).toBe(923)
})
it('parses trailing segments with query, fragment, and repeated slashes', () => {
expect(parseGitHubIssueOrPRNumber('https://github.com/o/r/pull/1965/changes?diff=split')).toBe(
1965
)
expect(
parseGitHubIssueOrPRNumber('https://github.com/o/r/issues/923/comments#issuecomment-1')
).toBe(923)
expect(parseGitHubIssueOrPRNumber('https://github.com/o/r/pull/1965//changes///')).toBe(1965)
expect(parseGitHubIssueOrPRNumber('https://github.com/o/r/issues/923///')).toBe(923)
})
it('rejects invalid GitHub item URLs', () => {
expect(parseGitHubIssueOrPRNumber('0')).toBeNull()
expect(parseGitHubIssueOrPRNumber('#0')).toBeNull()
expect(parseGitHubIssueOrPRNumber('https://github.com/o/r/pull/0')).toBeNull()
expect(
parseGitHubIssueOrPRNumber('https://github.com/o/r/pull/not-a-number/changes')
).toBeNull()
expect(parseGitHubIssueOrPRNumber('https://github.com/o/r/pull/')).toBeNull()
expect(parseGitHubIssueOrPRNumber('https://github.com/o/r/issues/123abc')).toBeNull()
expect(parseGitHubIssueOrPRNumber('https://github.com/owner/repo/pulls/123')).toBeNull()
})
})
describe('parseGitHubIssueOrPRLink', () => {
it('parses slug, number, and type for direct item URLs', () => {
expect(parseGitHubIssueOrPRLink('https://github.com/stablyai/orca/pull/123')).toEqual({
slug: { owner: 'stablyai', repo: 'orca' },
number: 123,
type: 'pr'
})
expect(
parseGitHubIssueOrPRLink('https://github.my-company.net/MyOrg/my_repo/pull/395')
).toEqual({
slug: { owner: 'MyOrg', repo: 'my_repo' },
number: 395,
type: 'pr'
})
expect(parseGitHubIssueOrPRLink('https://git.corp.com/MyOrg/my_repo/pull/395')).toEqual({
slug: { owner: 'MyOrg', repo: 'my_repo' },
number: 395,
type: 'pr'
})
expect(parseGitHubIssueOrPRLink('https://github.com/stablyai/orca/issues/923')).toEqual({
slug: { owner: 'stablyai', repo: 'orca' },
number: 923,
type: 'issue'
})
})
it('derives item type from the route segment when trailing segments are present', () => {
expect(parseGitHubIssueOrPRLink('https://github.com/o/r/pull/1965/changes')).toEqual({
slug: { owner: 'o', repo: 'r' },
number: 1965,
type: 'pr'
})
expect(parseGitHubIssueOrPRLink('https://github.com/o/r/issues/923/comments')).toEqual({
slug: { owner: 'o', repo: 'r' },
number: 923,
type: 'issue'
})
})
it('accepts query, fragment, and repeated trailing slashes', () => {
expect(parseGitHubIssueOrPRLink('https://github.com/o/r/pull/1965/files?plain=1#diff')).toEqual(
{
slug: { owner: 'o', repo: 'r' },
number: 1965,
type: 'pr'
}
)
expect(parseGitHubIssueOrPRLink('https://github.com/o/r/issues/923/comments///')).toEqual({
slug: { owner: 'o', repo: 'r' },
number: 923,
type: 'issue'
})
})
it('rejects non-GitHub and malformed item URLs', () => {
expect(parseGitHubIssueOrPRLink('https://github.com/o/r/pull/0')).toBeNull()
expect(parseGitHubIssueOrPRLink('https://github.com/o/r/issues/0')).toBeNull()
expect(parseGitHubIssueOrPRLink('https://github.com/o/r/pull/not-a-number/changes')).toBeNull()
expect(parseGitHubIssueOrPRLink('https://github.com/o/r/pull/')).toBeNull()
expect(parseGitHubIssueOrPRLink('https://github.com/o/r/issues/123abc')).toBeNull()
expect(parseGitHubIssueOrPRLink('https://github.com/owner/repo/pulls/123')).toBeNull()
})
})
describe('normalizeGitHubLinkQuery', () => {
it('accepts full GitHub URLs whose slug differs from the selected repo slug', () => {
expect(normalizeGitHubLinkQuery('https://github.com/stablyai/orca/issues/923')).toEqual({
query: 'https://github.com/stablyai/orca/issues/923',
directNumber: 923,
directLink: {
slug: { owner: 'stablyai', repo: 'orca' },
number: 923,
type: 'issue'
}
})
})
it('preserves PR route intent for full GitHub URLs', () => {
expect(normalizeGitHubLinkQuery('https://github.com/stablyai/orca/pull/6934')).toEqual({
query: 'https://github.com/stablyai/orca/pull/6934',
directNumber: 6934,
directLink: {
slug: { owner: 'stablyai', repo: 'orca' },
number: 6934,
type: 'pr'
}
})
})
it('preserves route intent for URLs with uppercase schemes', () => {
expect(normalizeGitHubLinkQuery('HTTPS://github.com/stablyai/orca/pull/6934')).toEqual({
query: 'HTTPS://github.com/stablyai/orca/pull/6934',
directNumber: 6934,
directLink: {
slug: { owner: 'stablyai', repo: 'orca' },
number: 6934,
type: 'pr'
}
})
})
it('rejects oversized pasted link queries without echoing their content', () => {
const secret = 'github-link-secret'
const result = normalizeGitHubLinkQuery(secret + 'x'.repeat(WORK_ITEM_LINK_QUERY_MAX_BYTES))
expect(result).toEqual({ query: '', directNumber: null, tooLarge: true })
})
it('rejects oversized whitespace before trimming link queries', () => {
expect(normalizeGitHubLinkQuery(' '.repeat(WORK_ITEM_LINK_QUERY_MAX_BYTES + 1))).toEqual({
query: '',
directNumber: null,
tooLarge: true
})
})
})