Extract GitLab project ref tests and update usage stats translations

- Move GitLab project ref parsing tests into a dedicated test file to
  keep modules focused and add tests for candidate parsing.
- Add missing translations for the usage sessions table and breakdown
  section across multiple locales.
This commit is contained in:
Jinjing
2026-06-11 19:29:51 -07:00
parent a9b86d8725
commit d13b664be0
8 changed files with 180 additions and 86 deletions
-85
View File
@@ -1,4 +1,3 @@
/* eslint-disable max-lines -- Why: GitLab remote parsing coverage needs many URL/host fixtures against the same mocked git/glab helpers. */
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
const { gitExecFileAsyncMock, glabExecFileAsyncMock, sshExecMock } = vi.hoisted(() => ({
@@ -22,96 +21,12 @@ import {
getGlabKnownHosts,
getProjectRef,
getProjectRefForRemote,
parseGitLabProjectRef,
parseGlabApiResponse,
parseGlabAuthStatusHosts,
resolveIssueSource
} from './gl-utils'
import { registerSshGitProvider, unregisterSshGitProvider } from '../providers/ssh-git-dispatch'
describe('gitlab project ref parsing', () => {
it('parses HTTPS and SSH GitLab.com remotes', () => {
expect(parseGitLabProjectRef('https://gitlab.com/acme/widgets.git')).toEqual({
host: 'gitlab.com',
path: 'acme/widgets'
})
expect(parseGitLabProjectRef('git@gitlab.com:stablyai/orca.git')).toEqual({
host: 'gitlab.com',
path: 'stablyai/orca'
})
})
it('preserves nested group paths', () => {
expect(parseGitLabProjectRef('git@gitlab.com:group/subgroup/project.git')).toEqual({
host: 'gitlab.com',
path: 'group/subgroup/project'
})
expect(parseGitLabProjectRef('https://gitlab.com/g1/g2/g3/proj.git')).toEqual({
host: 'gitlab.com',
path: 'g1/g2/g3/proj'
})
})
it('returns null for non-GitLab hosts when host not in knownHosts', () => {
expect(parseGitLabProjectRef('git@github.com:stablyai/orca.git')).toBeNull()
expect(parseGitLabProjectRef('git@example.com:foo/bar.git')).toBeNull()
})
it('matches self-hosted hosts when included in knownHosts', () => {
expect(
parseGitLabProjectRef('git@gitlab.example.com:team/api.git', [
'gitlab.com',
'gitlab.example.com'
])
).toEqual({ host: 'gitlab.example.com', path: 'team/api' })
})
it('parses GitLab remotes with non-standard ports without treating the port as a path segment', () => {
expect(
parseGitLabProjectRef('ssh://git@gitlab.example.com:2222/team/api.git', [
'gitlab.com',
'gitlab.example.com'
])
).toEqual({ host: 'gitlab.example.com', path: 'team/api' })
expect(
parseGitLabProjectRef('https://gitlab.example.com:8443/team/api.git', [
'gitlab.com',
'gitlab.example.com'
])
).toEqual({ host: 'gitlab.example.com', path: 'team/api' })
})
it('rejects single-segment paths (host root or user-only)', () => {
expect(parseGitLabProjectRef('git@gitlab.com:foo.git')).toBeNull()
expect(parseGitLabProjectRef('https://gitlab.com/foo.git')).toBeNull()
})
it('handles missing .git suffix', () => {
expect(parseGitLabProjectRef('https://gitlab.com/acme/widgets')).toEqual({
host: 'gitlab.com',
path: 'acme/widgets'
})
})
it('strips trailing slashes after .git suffixes', () => {
expect(parseGitLabProjectRef('https://gitlab.com/acme/widgets.git/')).toEqual({
host: 'gitlab.com',
path: 'acme/widgets'
})
expect(parseGitLabProjectRef('ssh://git@gitlab.com/acme/widgets.git/')).toEqual({
host: 'gitlab.com',
path: 'acme/widgets'
})
})
it('preserves git protocol remote support', () => {
expect(parseGitLabProjectRef('git://gitlab.com/acme/widgets.git')).toEqual({
host: 'gitlab.com',
path: 'acme/widgets'
})
})
})
describe('gitlab project ref resolution', () => {
beforeEach(() => {
gitExecFileAsyncMock.mockReset()
@@ -0,0 +1,99 @@
import { describe, expect, it } from 'vitest'
import { parseGitLabProjectRef, parseRemoteProjectRefCandidate } from './project-ref-parser'
describe('gitlab project ref parsing', () => {
it('parses HTTPS and SSH GitLab.com remotes', () => {
expect(parseGitLabProjectRef('https://gitlab.com/acme/widgets.git')).toEqual({
host: 'gitlab.com',
path: 'acme/widgets'
})
expect(parseGitLabProjectRef('git@gitlab.com:stablyai/orca.git')).toEqual({
host: 'gitlab.com',
path: 'stablyai/orca'
})
})
it('preserves nested group paths', () => {
expect(parseGitLabProjectRef('git@gitlab.com:group/subgroup/project.git')).toEqual({
host: 'gitlab.com',
path: 'group/subgroup/project'
})
expect(parseGitLabProjectRef('https://gitlab.com/g1/g2/g3/proj.git')).toEqual({
host: 'gitlab.com',
path: 'g1/g2/g3/proj'
})
})
it('returns null for non-GitLab hosts when host not in knownHosts', () => {
expect(parseGitLabProjectRef('git@github.com:stablyai/orca.git')).toBeNull()
expect(parseGitLabProjectRef('git@example.com:foo/bar.git')).toBeNull()
})
it('matches self-hosted hosts when included in knownHosts', () => {
expect(
parseGitLabProjectRef('git@gitlab.example.com:team/api.git', [
'gitlab.com',
'gitlab.example.com'
])
).toEqual({ host: 'gitlab.example.com', path: 'team/api' })
})
it('parses GitLab remotes with non-standard ports without treating the port as a path segment', () => {
expect(
parseGitLabProjectRef('ssh://git@gitlab.example.com:2222/team/api.git', [
'gitlab.com',
'gitlab.example.com'
])
).toEqual({ host: 'gitlab.example.com', path: 'team/api' })
expect(
parseGitLabProjectRef('https://gitlab.example.com:8443/team/api.git', [
'gitlab.com',
'gitlab.example.com'
])
).toEqual({ host: 'gitlab.example.com', path: 'team/api' })
})
it('rejects single-segment paths (host root or user-only)', () => {
expect(parseGitLabProjectRef('git@gitlab.com:foo.git')).toBeNull()
expect(parseGitLabProjectRef('https://gitlab.com/foo.git')).toBeNull()
})
it('handles missing .git suffix', () => {
expect(parseGitLabProjectRef('https://gitlab.com/acme/widgets')).toEqual({
host: 'gitlab.com',
path: 'acme/widgets'
})
})
it('strips trailing slashes after .git suffixes', () => {
expect(parseGitLabProjectRef('https://gitlab.com/acme/widgets.git/')).toEqual({
host: 'gitlab.com',
path: 'acme/widgets'
})
expect(parseGitLabProjectRef('ssh://git@gitlab.com/acme/widgets.git/')).toEqual({
host: 'gitlab.com',
path: 'acme/widgets'
})
})
it('preserves git protocol remote support', () => {
expect(parseGitLabProjectRef('git://gitlab.com/acme/widgets.git')).toEqual({
host: 'gitlab.com',
path: 'acme/widgets'
})
})
})
describe('gitlab remote project ref candidates', () => {
it('extracts self-hosted candidates before the host is trusted', () => {
expect(parseRemoteProjectRefCandidate('git@gitlab.internal:team/orca.git')).toEqual({
host: 'gitlab.internal',
path: 'team/orca'
})
})
it('rejects non-git URLs and single-segment project paths', () => {
expect(parseRemoteProjectRefCandidate('file:///tmp/repo')).toBeNull()
expect(parseRemoteProjectRefCandidate('git@gitlab.internal:team.git')).toBeNull()
})
})
@@ -69,7 +69,7 @@ export function UsageSessionsTable({
{translate('auto.components.stats.UsageSessionsTable.c17bed0416', 'Project')}
</th>
<th className="px-2 py-2 font-medium">
{translate('auto.components.stats.UsageSessionsTable.1afc25eb06', 'Model')}
{translate('auto.components.stats.UsageSessionsTable.f6a2c8d019', 'Model')}
</th>
<th className="px-2 py-2 font-medium">{eventsLabel}</th>
<th className="px-2 py-2 font-medium">
+16
View File
@@ -2991,6 +2991,22 @@
"d9a4b3e2f1c5": "events"
}
}
},
"UsageBreakdownSection": {
"7765a4c3e1": "n/a",
"247c93ca92": "• inferred pricing"
},
"UsageSessionsTable": {
"1afc25eb06": "Turns",
"0f03975d59": "Events",
"21ea00bfa8": "Cache",
"e0b988599d": "Total",
"01476891c7": "Last active",
"c17bed0416": "Project",
"f6a2c8d019": "Model",
"faf3444859": "Input",
"a8b7487ff7": "Output",
"cfe2282ffa": "Unknown"
}
},
"sparse": {
+16
View File
@@ -2991,6 +2991,22 @@
"d9a4b3e2f1c5": "eventos"
}
}
},
"UsageBreakdownSection": {
"7765a4c3e1": "n / A",
"247c93ca92": "• precios inferidos"
},
"UsageSessionsTable": {
"1afc25eb06": "vueltas",
"0f03975d59": "Eventos",
"21ea00bfa8": "Cache",
"e0b988599d": "Total",
"01476891c7": "Último activo",
"c17bed0416": "Proyecto",
"f6a2c8d019": "Modelo",
"faf3444859": "Aporte",
"a8b7487ff7": "Producción",
"cfe2282ffa": "Desconocido"
}
},
"sparse": {
+16
View File
@@ -2991,6 +2991,22 @@
"d9a4b3e2f1c5": "イベント"
}
}
},
"UsageBreakdownSection": {
"7765a4c3e1": "該当なし",
"247c93ca92": "• 推定価格"
},
"UsageSessionsTable": {
"1afc25eb06": "ターン",
"0f03975d59": "イベント",
"21ea00bfa8": "キャッシュ",
"e0b988599d": "合計",
"01476891c7": "最後にアクティブだった",
"c17bed0416": "プロジェクト",
"f6a2c8d019": "モデル",
"faf3444859": "入力",
"a8b7487ff7": "出力",
"cfe2282ffa": "未知"
}
},
"sparse": {
+16
View File
@@ -2991,6 +2991,22 @@
"d9a4b3e2f1c5": "이벤트"
}
}
},
"UsageBreakdownSection": {
"7765a4c3e1": "해당 없음",
"247c93ca92": "• 추론된 가격"
},
"UsageSessionsTable": {
"1afc25eb06": "턴",
"0f03975d59": "이벤트",
"21ea00bfa8": "캐시",
"e0b988599d": "총",
"01476891c7": "마지막 활동",
"c17bed0416": "프로젝트",
"f6a2c8d019": "모델",
"faf3444859": "입력",
"a8b7487ff7": "출력",
"cfe2282ffa": "알려지지 않은"
}
},
"sparse": {
+16
View File
@@ -2991,6 +2991,22 @@
"d9a4b3e2f1c5": "事件"
}
}
},
"UsageBreakdownSection": {
"7765a4c3e1": "不适用",
"247c93ca92": "• 推断定价"
},
"UsageSessionsTable": {
"1afc25eb06": "轮次",
"0f03975d59": "事件",
"21ea00bfa8": "缓存",
"e0b988599d": "总计",
"01476891c7": "最后活跃",
"c17bed0416": "项目",
"f6a2c8d019": "模型",
"faf3444859": "输入",
"a8b7487ff7": "输出",
"cfe2282ffa": "未知"
}
},
"sparse": {