Full parity with the existing GitLab provider: - Server module packages/web/server/lib/gitea (auth/client/repo/routes + docs + tests) with Gitea REST v1 API, PAT + base URL auth, multi-account storage - Shared GiteaAPI types and web API client - Provider detection generalized with user-configurable custom domains per provider (github/gitlab/gitea), additive with built-in defaults (github.com, gitlab.com) and connected-account hosts; precedence github -> gitlab -> gitea - Gitea PR view, issues section, pickers, integration dialog, branch PR status helper, settings UI (PAT + base URL + custom domains) - Magic prompts (gitea.pr.review, gitea.issue.review) and full 11-locale i18n parity
121 lines
3.7 KiB
TypeScript
121 lines
3.7 KiB
TypeScript
import { afterEach, beforeEach, describe, expect, mock, test } from 'bun:test';
|
|
import type { GiteaPullRequestSummary } from '@/lib/api/types';
|
|
|
|
const prsListCalls: Array<{ directory: string; options?: { sourceBranch?: string } }> = [];
|
|
let prsListResult: GiteaPullRequestSummary[] = [];
|
|
let prsListFailure: Error | null = null;
|
|
let registryHasGitea = true;
|
|
|
|
mock.module('@/contexts/runtimeAPIRegistry', () => ({
|
|
getRegisteredRuntimeAPIs: () => {
|
|
if (!registryHasGitea) return null;
|
|
return {
|
|
gitea: {
|
|
prsList: (directory: string, options?: { sourceBranch?: string }) => {
|
|
prsListCalls.push({ directory, options });
|
|
if (prsListFailure) {
|
|
return Promise.reject(prsListFailure);
|
|
}
|
|
const all = prsListResult;
|
|
const filtered = options?.sourceBranch
|
|
? all.filter((item) => item.sourceBranch === options.sourceBranch)
|
|
: all;
|
|
return Promise.resolve({ prs: filtered });
|
|
},
|
|
},
|
|
};
|
|
},
|
|
}));
|
|
|
|
const { resolveGiteaPrForBranch } = await import('./giteaPrStatus');
|
|
|
|
const pr = (number: number, state: 'open' | 'closed' | 'merged', sourceBranch: string): GiteaPullRequestSummary => ({
|
|
number,
|
|
title: `PR ${number}`,
|
|
url: `https://gitea.example/${number}`,
|
|
state,
|
|
draft: false,
|
|
author: { id: 1, username: 'user' },
|
|
labels: [],
|
|
sourceBranch,
|
|
targetBranch: 'main',
|
|
});
|
|
|
|
describe('resolveGiteaPrForBranch', () => {
|
|
beforeEach(() => {
|
|
prsListCalls.length = 0;
|
|
prsListResult = [];
|
|
prsListFailure = null;
|
|
registryHasGitea = true;
|
|
});
|
|
|
|
afterEach(() => {
|
|
prsListCalls.length = 0;
|
|
prsListResult = [];
|
|
prsListFailure = null;
|
|
registryHasGitea = true;
|
|
});
|
|
|
|
test('prefers the open PR over a merged one for the branch', async () => {
|
|
prsListResult = [pr(3, 'merged', 'feat/a'), pr(7, 'open', 'feat/a')];
|
|
|
|
const result = await resolveGiteaPrForBranch('/repo', 'feat/a');
|
|
|
|
expect(result?.number).toBe(7);
|
|
expect(prsListCalls).toEqual([
|
|
{ directory: '/repo', options: { sourceBranch: 'feat/a' } },
|
|
]);
|
|
});
|
|
|
|
test('falls back to a merged PR when no open one exists', async () => {
|
|
prsListResult = [pr(5, 'merged', 'feat/b'), pr(9, 'closed', 'feat/b')];
|
|
|
|
const result = await resolveGiteaPrForBranch('/repo', 'feat/b');
|
|
|
|
expect(result?.number).toBe(5);
|
|
});
|
|
|
|
test('returns null when no PR matches the branch', async () => {
|
|
prsListResult = [pr(5, 'merged', 'feat/a')];
|
|
|
|
const result = await resolveGiteaPrForBranch('/repo', 'feat/c');
|
|
|
|
expect(result).toBeNull();
|
|
expect(prsListCalls).toHaveLength(1);
|
|
});
|
|
|
|
test('returns null without calling the API when the runtime has no Gitea client', async () => {
|
|
registryHasGitea = false;
|
|
|
|
const result = await resolveGiteaPrForBranch('/repo', 'feat/a');
|
|
|
|
expect(result).toBeNull();
|
|
expect(prsListCalls).toHaveLength(0);
|
|
});
|
|
|
|
test('returns null and caches when the request fails', async () => {
|
|
prsListFailure = new Error('boom');
|
|
|
|
const first = await resolveGiteaPrForBranch('/repo', 'fail-branch');
|
|
expect(first).toBeNull();
|
|
|
|
prsListFailure = null;
|
|
prsListResult = [pr(1, 'open', 'fail-branch')];
|
|
// Same directory+branch within TTL must not re-request.
|
|
const second = await resolveGiteaPrForBranch('/repo', 'fail-branch');
|
|
expect(second).toBeNull();
|
|
expect(prsListCalls).toHaveLength(1);
|
|
});
|
|
|
|
test('serves the second call from cache within the TTL window', async () => {
|
|
prsListResult = [pr(7, 'open', 'cache-branch')];
|
|
|
|
const first = await resolveGiteaPrForBranch('/repo', 'cache-branch');
|
|
const second = await resolveGiteaPrForBranch('/repo', 'cache-branch');
|
|
|
|
expect(first?.number).toBe(7);
|
|
expect(second?.number).toBe(7);
|
|
expect(prsListCalls).toHaveLength(1);
|
|
});
|
|
});
|