@shipfox/api-integration-gitea 12.5.0 → 14.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@shipfox/api-integration-gitea",
3
3
  "license": "MIT",
4
- "version": "12.5.0",
4
+ "version": "14.0.0",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "git+https://github.com/ShipfoxHQ/shipfox.git",
@@ -20,8 +20,8 @@
20
20
  "dependencies": {
21
21
  "drizzle-orm": "^0.45.2",
22
22
  "@shipfox/api-auth-context": "12.2.0",
23
- "@shipfox/api-integration-spi": "1.1.1",
24
- "@shipfox/api-integration-gitea-dto": "12.2.0",
23
+ "@shipfox/api-integration-spi": "2.0.0",
24
+ "@shipfox/api-integration-gitea-dto": "14.0.0",
25
25
  "@shipfox/config": "1.2.4",
26
26
  "@shipfox/node-drizzle": "0.3.5",
27
27
  "@shipfox/node-fastify": "0.4.2",
@@ -115,6 +115,64 @@ describe('HttpGiteaApiClient', () => {
115
115
  await expect(result).rejects.toMatchObject({reason: 'repository-not-found'});
116
116
  });
117
117
 
118
+ it('gets a branch head commit', async () => {
119
+ fetchMock.mockResolvedValue(
120
+ jsonResponse({name: 'main', commit: {id: 'abc123', message: 'hi'}}),
121
+ );
122
+ const client = createGiteaApiClient();
123
+
124
+ const result = await client.getBranch({owner: 'shipfox', repo: 'platform', branch: 'main'});
125
+
126
+ expect(result).toEqual({commitSha: 'abc123'});
127
+ expect(requestedUrl().pathname).toBe('/api/v1/repos/shipfox/platform/branches/main');
128
+ });
129
+
130
+ it('gets a tag head commit', async () => {
131
+ fetchMock.mockResolvedValue(jsonResponse({name: 'v1.0.0', commit: {sha: 'abc123'}}));
132
+ const client = createGiteaApiClient();
133
+
134
+ const result = await client.getTag({owner: 'shipfox', repo: 'platform', tag: 'v1.0.0'});
135
+
136
+ expect(result).toEqual({commitSha: 'abc123'});
137
+ expect(requestedUrl().pathname).toBe('/api/v1/repos/shipfox/platform/tags/v1.0.0');
138
+ });
139
+
140
+ it('maps a missing branch to ref-not-found', async () => {
141
+ fetchMock.mockResolvedValue(jsonResponse({message: 'not found'}, {status: 404}));
142
+ const client = createGiteaApiClient();
143
+
144
+ const result = client.getBranch({owner: 'shipfox', repo: 'platform', branch: 'missing'});
145
+
146
+ await expect(result).rejects.toMatchObject({reason: 'ref-not-found'});
147
+ });
148
+
149
+ it('maps a missing tag to ref-not-found', async () => {
150
+ fetchMock.mockResolvedValue(jsonResponse({message: 'not found'}, {status: 404}));
151
+ const client = createGiteaApiClient();
152
+
153
+ const result = client.getTag({owner: 'shipfox', repo: 'platform', tag: 'missing'});
154
+
155
+ await expect(result).rejects.toMatchObject({reason: 'ref-not-found'});
156
+ });
157
+
158
+ it('rejects a branch response without a head commit', async () => {
159
+ fetchMock.mockResolvedValue(jsonResponse({name: 'main', commit: {}}));
160
+ const client = createGiteaApiClient();
161
+
162
+ const result = client.getBranch({owner: 'shipfox', repo: 'platform', branch: 'main'});
163
+
164
+ await expect(result).rejects.toMatchObject({reason: 'malformed-provider-response'});
165
+ });
166
+
167
+ it('rejects a tag response without a head commit', async () => {
168
+ fetchMock.mockResolvedValue(jsonResponse({name: 'v1.0.0', commit: {}}));
169
+ const client = createGiteaApiClient();
170
+
171
+ const result = client.getTag({owner: 'shipfox', repo: 'platform', tag: 'v1.0.0'});
172
+
173
+ await expect(result).rejects.toMatchObject({reason: 'malformed-provider-response'});
174
+ });
175
+
118
176
  it('lists the recursive tree, keeping blobs and dropping subtrees', async () => {
119
177
  fetchMock.mockResolvedValue(
120
178
  jsonResponse({
package/src/api/client.ts CHANGED
@@ -41,6 +41,10 @@ export interface GiteaFileContent {
41
41
  size: number;
42
42
  }
43
43
 
44
+ export interface GiteaRefCommit {
45
+ commitSha: string;
46
+ }
47
+
44
48
  export interface GiteaApiClient {
45
49
  listOrgRepositories(input: {
46
50
  org: string;
@@ -49,6 +53,8 @@ export interface GiteaApiClient {
49
53
  }): Promise<GiteaRepositoryPage>;
50
54
  getRepository(input: {owner: string; repo: string}): Promise<GiteaRepository>;
51
55
  resolveRef(input: {owner: string; repo: string; ref: string}): Promise<string>;
56
+ getBranch(input: {owner: string; repo: string; branch: string}): Promise<GiteaRefCommit>;
57
+ getTag(input: {owner: string; repo: string; tag: string}): Promise<GiteaRefCommit>;
52
58
  listTree(input: {owner: string; repo: string; sha: string}): Promise<GiteaTree>;
53
59
  fetchFileContent(input: {
54
60
  owner: string;
@@ -113,6 +119,40 @@ class HttpGiteaApiClient implements GiteaApiClient {
113
119
  return head.sha;
114
120
  }
115
121
 
122
+ async getBranch(input: {owner: string; repo: string; branch: string}): Promise<GiteaRefCommit> {
123
+ const response = await this.request(
124
+ `repos/${encodeURIComponent(input.owner)}/${encodeURIComponent(input.repo)}/branches/${encodeURIComponent(input.branch)}`,
125
+ {},
126
+ {notFoundReason: 'ref-not-found'},
127
+ );
128
+ const data = await response.json();
129
+ const commit = isRecord(data) ? data.commit : undefined;
130
+ if (!isRecord(commit) || typeof commit.id !== 'string') {
131
+ throw new GiteaIntegrationProviderError(
132
+ 'malformed-provider-response',
133
+ `Gitea branch ${input.branch} response is missing the head commit`,
134
+ );
135
+ }
136
+ return {commitSha: commit.id};
137
+ }
138
+
139
+ async getTag(input: {owner: string; repo: string; tag: string}): Promise<GiteaRefCommit> {
140
+ const response = await this.request(
141
+ `repos/${encodeURIComponent(input.owner)}/${encodeURIComponent(input.repo)}/tags/${encodeURIComponent(input.tag)}`,
142
+ {},
143
+ {notFoundReason: 'ref-not-found'},
144
+ );
145
+ const data = await response.json();
146
+ const commit = isRecord(data) ? data.commit : undefined;
147
+ if (!isRecord(commit) || typeof commit.sha !== 'string') {
148
+ throw new GiteaIntegrationProviderError(
149
+ 'malformed-provider-response',
150
+ `Gitea tag ${input.tag} response is missing the head commit`,
151
+ );
152
+ }
153
+ return {commitSha: commit.sha};
154
+ }
155
+
116
156
  async listTree(input: {owner: string; repo: string; sha: string}): Promise<GiteaTree> {
117
157
  const response = await this.request(
118
158
  `repos/${encodeURIComponent(input.owner)}/${encodeURIComponent(input.repo)}/git/trees/${encodeURIComponent(input.sha)}`,
@@ -264,7 +304,7 @@ class HttpGiteaApiClient implements GiteaApiClient {
264
304
  }
265
305
  }
266
306
 
267
- type NotFoundReason = 'repository-not-found' | 'file-not-found';
307
+ type NotFoundReason = 'repository-not-found' | 'file-not-found' | 'ref-not-found';
268
308
 
269
309
  function giteaHttpError(
270
310
  response: Response,
@@ -6,6 +6,8 @@ function giteaClient(): GiteaApiClient {
6
6
  listOrgRepositories: vi.fn(() => Promise.reject(new Error('not used'))),
7
7
  getRepository: vi.fn(() => Promise.reject(new Error('not used'))),
8
8
  resolveRef: vi.fn(() => Promise.reject(new Error('not used'))),
9
+ getBranch: vi.fn(() => Promise.reject(new Error('not used'))),
10
+ getTag: vi.fn(() => Promise.reject(new Error('not used'))),
9
11
  listTree: vi.fn(() => Promise.reject(new Error('not used'))),
10
12
  fetchFileContent: vi.fn(() => Promise.reject(new Error('not used'))),
11
13
  organizationExists: vi.fn(() => Promise.reject(new Error('not used'))),
@@ -8,6 +8,8 @@ function giteaClient(overrides: Partial<GiteaApiClient> = {}): GiteaApiClient {
8
8
  listOrgRepositories: vi.fn(() => Promise.reject(new Error('not used'))),
9
9
  getRepository: vi.fn(() => Promise.reject(new Error('not used'))),
10
10
  resolveRef: vi.fn(() => Promise.reject(new Error('not used'))),
11
+ getBranch: vi.fn(() => Promise.reject(new Error('not used'))),
12
+ getTag: vi.fn(() => Promise.reject(new Error('not used'))),
11
13
  listTree: vi.fn(() => Promise.reject(new Error('not used'))),
12
14
  fetchFileContent: vi.fn(() => Promise.reject(new Error('not used'))),
13
15
  organizationExists: vi.fn(() => Promise.resolve(true)),
@@ -16,6 +16,8 @@ function giteaClient(repository: GiteaRepository): GiteaApiClient {
16
16
  listOrgRepositories: vi.fn(),
17
17
  getRepository: vi.fn(() => Promise.resolve(repository)),
18
18
  resolveRef: vi.fn(),
19
+ getBranch: vi.fn(),
20
+ getTag: vi.fn(),
19
21
  listTree: vi.fn(),
20
22
  fetchFileContent: vi.fn(),
21
23
  organizationExists: vi.fn(),
@@ -35,6 +35,8 @@ function giteaClient(overrides: Partial<GiteaApiClient> = {}): GiteaApiClient {
35
35
  size: 58,
36
36
  }),
37
37
  ),
38
+ getBranch: vi.fn(() => Promise.resolve({commitSha: 'branch-head'})),
39
+ getTag: vi.fn(() => Promise.reject(new Error('not used'))),
38
40
  organizationExists: vi.fn(() => Promise.resolve(true)),
39
41
  ...overrides,
40
42
  };
@@ -168,6 +170,178 @@ describe('GiteaSourceControlProvider', () => {
168
170
  expect(result).toBeNull();
169
171
  });
170
172
 
173
+ it('resolves a branch ref to the commit it points at', async () => {
174
+ const gitea = giteaClient();
175
+ const provider = new GiteaSourceControlProvider(gitea);
176
+
177
+ const result = await provider.resolveRef({
178
+ connection: connection(),
179
+ externalRepositoryId: 'gitea:shipfox/platform',
180
+ ref: 'refs/heads/main',
181
+ });
182
+
183
+ expect(result).toEqual({ref: 'refs/heads/main', commit: 'branch-head'});
184
+ expect(gitea.getBranch).toHaveBeenCalledWith({
185
+ owner: 'shipfox',
186
+ repo: 'platform',
187
+ branch: 'main',
188
+ });
189
+ expect(gitea.getTag).not.toHaveBeenCalled();
190
+ });
191
+
192
+ it('does not cross namespaces for a missing namespaced branch ref', async () => {
193
+ const gitea = giteaClient({
194
+ getBranch: vi.fn(() =>
195
+ Promise.reject(new GiteaIntegrationProviderError('ref-not-found', 'no branch')),
196
+ ),
197
+ getTag: vi.fn(() => Promise.resolve({commitSha: VALID_COMMIT})),
198
+ });
199
+ const provider = new GiteaSourceControlProvider(gitea);
200
+
201
+ const result = provider.resolveRef({
202
+ connection: connection(),
203
+ externalRepositoryId: 'gitea:shipfox/platform',
204
+ ref: 'refs/heads/release',
205
+ });
206
+
207
+ await expect(result).rejects.toMatchObject({reason: 'ref-not-found'});
208
+ expect(gitea.getBranch).toHaveBeenCalledWith({
209
+ owner: 'shipfox',
210
+ repo: 'platform',
211
+ branch: 'release',
212
+ });
213
+ expect(gitea.getTag).not.toHaveBeenCalled();
214
+ });
215
+
216
+ it('uses the tag endpoint first for a tag ref', async () => {
217
+ const gitea = giteaClient({
218
+ getTag: vi.fn(() => Promise.resolve({commitSha: VALID_COMMIT})),
219
+ });
220
+ const provider = new GiteaSourceControlProvider(gitea);
221
+
222
+ const result = await provider.resolveRef({
223
+ connection: connection(),
224
+ externalRepositoryId: 'gitea:shipfox/platform',
225
+ ref: 'refs/tags/v1.0.0',
226
+ });
227
+
228
+ expect(result).toEqual({ref: 'refs/tags/v1.0.0', commit: VALID_COMMIT});
229
+ expect(gitea.getTag).toHaveBeenCalledWith({
230
+ owner: 'shipfox',
231
+ repo: 'platform',
232
+ tag: 'v1.0.0',
233
+ });
234
+ expect(gitea.getBranch).not.toHaveBeenCalled();
235
+ });
236
+
237
+ it('maps a ref that is neither branch nor tag to ref-not-found', async () => {
238
+ const gitea = giteaClient({
239
+ getBranch: vi.fn(() =>
240
+ Promise.reject(new GiteaIntegrationProviderError('ref-not-found', 'no branch')),
241
+ ),
242
+ getTag: vi.fn(() =>
243
+ Promise.reject(new GiteaIntegrationProviderError('ref-not-found', 'no tag')),
244
+ ),
245
+ });
246
+ const provider = new GiteaSourceControlProvider(gitea);
247
+
248
+ const result = provider.resolveRef({
249
+ connection: connection(),
250
+ externalRepositoryId: 'gitea:shipfox/platform',
251
+ ref: 'feature/missing',
252
+ });
253
+
254
+ await expect(result).rejects.toMatchObject({reason: 'ref-not-found'});
255
+ });
256
+
257
+ it('falls back to a tag for an unnamespaced ref', async () => {
258
+ const gitea = giteaClient({
259
+ getBranch: vi.fn(() =>
260
+ Promise.reject(new GiteaIntegrationProviderError('ref-not-found', 'no branch')),
261
+ ),
262
+ getTag: vi.fn(() => Promise.resolve({commitSha: VALID_COMMIT})),
263
+ });
264
+ const provider = new GiteaSourceControlProvider(gitea);
265
+
266
+ const result = await provider.resolveRef({
267
+ connection: connection(),
268
+ externalRepositoryId: 'gitea:shipfox/platform',
269
+ ref: 'feature/release',
270
+ });
271
+
272
+ expect(result).toEqual({ref: 'feature/release', commit: VALID_COMMIT});
273
+ expect(gitea.getBranch).toHaveBeenCalledWith({
274
+ owner: 'shipfox',
275
+ repo: 'platform',
276
+ branch: 'feature/release',
277
+ });
278
+ expect(gitea.getTag).toHaveBeenCalledWith({
279
+ owner: 'shipfox',
280
+ repo: 'platform',
281
+ tag: 'feature/release',
282
+ });
283
+ expect(gitea.getRepository).not.toHaveBeenCalled();
284
+ });
285
+
286
+ it('preserves repository-not-found when the repository is missing', async () => {
287
+ const gitea = giteaClient({
288
+ getBranch: vi.fn(() =>
289
+ Promise.reject(new GiteaIntegrationProviderError('ref-not-found', 'no branch')),
290
+ ),
291
+ getRepository: vi.fn(() =>
292
+ Promise.reject(new GiteaIntegrationProviderError('repository-not-found', 'no repo')),
293
+ ),
294
+ });
295
+ const provider = new GiteaSourceControlProvider(gitea);
296
+
297
+ const result = provider.resolveRef({
298
+ connection: connection(),
299
+ externalRepositoryId: 'gitea:shipfox/platform',
300
+ ref: 'refs/heads/main',
301
+ });
302
+
303
+ await expect(result).rejects.toMatchObject({reason: 'repository-not-found'});
304
+ expect(gitea.getTag).not.toHaveBeenCalled();
305
+ });
306
+
307
+ it.each([
308
+ 'a'.repeat(40),
309
+ 'refs/pull/17/head',
310
+ 'main',
311
+ '-evil',
312
+ ])('rejects ref %s as ref-invalid', async (ref) => {
313
+ const gitea = giteaClient();
314
+ const provider = new GiteaSourceControlProvider(gitea);
315
+
316
+ const result = provider.resolveRef({
317
+ connection: connection(),
318
+ externalRepositoryId: 'gitea:shipfox/platform',
319
+ ref,
320
+ });
321
+
322
+ await expect(result).rejects.toMatchObject({reason: 'ref-invalid'});
323
+ expect(gitea.getBranch).not.toHaveBeenCalled();
324
+ expect(gitea.getTag).not.toHaveBeenCalled();
325
+ });
326
+
327
+ it('propagates non-not-found branch failures without a tag fallback', async () => {
328
+ const gitea = giteaClient({
329
+ getBranch: vi.fn(() =>
330
+ Promise.reject(new GiteaIntegrationProviderError('access-denied', 'denied')),
331
+ ),
332
+ });
333
+ const provider = new GiteaSourceControlProvider(gitea);
334
+
335
+ const result = provider.resolveRef({
336
+ connection: connection(),
337
+ externalRepositoryId: 'gitea:shipfox/platform',
338
+ ref: 'refs/heads/main',
339
+ });
340
+
341
+ await expect(result).rejects.toMatchObject({reason: 'access-denied'});
342
+ expect(gitea.getTag).not.toHaveBeenCalled();
343
+ });
344
+
171
345
  it('lists org repositories scoped to the connection account', async () => {
172
346
  const gitea = giteaClient();
173
347
  const provider = new GiteaSourceControlProvider(gitea);
@@ -11,6 +11,7 @@ import {
11
11
  type IntegrationConnection,
12
12
  isRecord,
13
13
  isValidGitObjectId,
14
+ isValidResolvableRef,
14
15
  isValidTriggerRef,
15
16
  type ListFilesInput,
16
17
  type ListRepositoriesInput,
@@ -21,6 +22,8 @@ import {
21
22
  type RepositoryPage,
22
23
  type RepositorySnapshot,
23
24
  type RepositoryVisibility,
25
+ type ResolvedRef,
26
+ type ResolveRefInput,
24
27
  type ResolveRepositoryInput,
25
28
  type SourceControlProvider,
26
29
  type TriggerReference,
@@ -32,6 +35,8 @@ import {GiteaIntegrationProviderError} from './errors.js';
32
35
  type GiteaIntegrationConnection = IntegrationConnection<'gitea'>;
33
36
 
34
37
  const TRAILING_SLASHES_RE = /\/+$/;
38
+ const REFS_HEADS_PREFIX = 'refs/heads/';
39
+ const REFS_TAGS_PREFIX = 'refs/tags/';
35
40
  const SEARCH_PAGE_SIZE = 100;
36
41
  const SEARCH_MAX_PAGES_PER_REQUEST = 5;
37
42
 
@@ -183,6 +188,44 @@ export class GiteaSourceControlProvider
183
188
  };
184
189
  }
185
190
 
191
+ async resolveRef(input: ResolveRefInput<GiteaIntegrationConnection>): Promise<ResolvedRef> {
192
+ if (!isValidResolvableRef(input.ref)) {
193
+ throw new GiteaIntegrationProviderError(
194
+ 'ref-invalid',
195
+ `Gitea ref ${formatRefForMessage(input.ref)} is not a resolvable branch or tag name`,
196
+ );
197
+ }
198
+ const {owner, repo} = parseGiteaRepositoryLocator(
199
+ input.externalRepositoryId,
200
+ input.connection.externalAccountId,
201
+ );
202
+
203
+ const providerRef = providerRefName(input.ref);
204
+ const branchLookup = () => this.gitea.getBranch({owner, repo, branch: providerRef});
205
+ const tagLookup = () => this.gitea.getTag({owner, repo, tag: providerRef});
206
+ const lookups: Array<() => Promise<{commitSha: string}>> = input.ref.startsWith(
207
+ REFS_TAGS_PREFIX,
208
+ )
209
+ ? [tagLookup]
210
+ : input.ref.startsWith(REFS_HEADS_PREFIX)
211
+ ? [branchLookup]
212
+ : [branchLookup, tagLookup];
213
+ for (const lookup of lookups) {
214
+ try {
215
+ const resolved = await lookup();
216
+ return {ref: input.ref, commit: resolved.commitSha};
217
+ } catch (error) {
218
+ if (!isRefNotFound(error)) throw error;
219
+ }
220
+ }
221
+
222
+ await this.gitea.getRepository({owner, repo});
223
+ throw new GiteaIntegrationProviderError(
224
+ 'ref-not-found',
225
+ `Gitea ref ${formatRefForMessage(input.ref)} does not resolve to a branch or tag`,
226
+ );
227
+ }
228
+
186
229
  async createCheckoutSpec(
187
230
  input: CreateCheckoutSpecInput<GiteaIntegrationConnection>,
188
231
  ): Promise<CheckoutSpec> {
@@ -225,6 +268,20 @@ function giteaEventActor(payload: Record<string, unknown>): string | null {
225
268
  return nonEmptyString(sender?.login) ?? nonEmptyString(sender?.username);
226
269
  }
227
270
 
271
+ function isRefNotFound(error: unknown): boolean {
272
+ return error instanceof GiteaIntegrationProviderError && error.reason === 'ref-not-found';
273
+ }
274
+
275
+ function providerRefName(ref: string): string {
276
+ if (ref.startsWith(REFS_HEADS_PREFIX)) return ref.slice(REFS_HEADS_PREFIX.length);
277
+ if (ref.startsWith(REFS_TAGS_PREFIX)) return ref.slice(REFS_TAGS_PREFIX.length);
278
+ return ref;
279
+ }
280
+
281
+ function formatRefForMessage(ref: string): string {
282
+ return JSON.stringify(ref);
283
+ }
284
+
228
285
  function sameGiteaRepository(
229
286
  first: Record<string, unknown>,
230
287
  second: Record<string, unknown>,
@@ -2,6 +2,7 @@ import {
2
2
  type GiteaPushPayloadDto,
3
3
  giteaProviderKind,
4
4
  giteaPushPayloadSchema,
5
+ giteaWebhookEventNames,
5
6
  } from '@shipfox/api-integration-gitea-dto';
6
7
  import {
7
8
  buildProviderRepositoryId,
@@ -67,7 +68,7 @@ function isBranchDeletion(after: string): boolean {
67
68
  export async function handleGiteaWebhook(
68
69
  params: HandleGiteaWebhookParams,
69
70
  ): Promise<{outcome: HandleGiteaWebhookOutcome}> {
70
- if (params.event !== 'push') {
71
+ if (!giteaWebhookEventNames.some((eventName) => eventName === params.event)) {
71
72
  await params.recordDeliveryOnly({
72
73
  tx: params.tx,
73
74
  provider: giteaProviderKind,
package/src/index.test.ts CHANGED
@@ -13,6 +13,7 @@ describe('createGiteaIntegrationProvider', () => {
13
13
 
14
14
  expect(provider.provider).toBe('gitea');
15
15
  expect(provider.displayName).toBe('Gitea');
16
+ expect(provider.eventCatalog?.events.map((event) => event.name)).toEqual(['push']);
16
17
  expect(provider.adapters.source_control).toBeInstanceOf(GiteaSourceControlProvider);
17
18
  expect(provider.routes).toHaveLength(2);
18
19
  expect(provider.routes.map((group) => group.prefix)).toEqual([
package/src/index.ts CHANGED
@@ -1,4 +1,4 @@
1
- import {giteaProviderKind} from '@shipfox/api-integration-gitea-dto';
1
+ import {giteaEventCatalog, giteaProviderKind} from '@shipfox/api-integration-gitea-dto';
2
2
  import type {
3
3
  GetIntegrationConnectionByIdFn,
4
4
  IntegrationConnection,
@@ -67,6 +67,7 @@ export function createGiteaIntegrationProvider(options: CreateGiteaIntegrationPr
67
67
  return {
68
68
  provider: giteaProviderKind,
69
69
  displayName: 'Gitea',
70
+ eventCatalog: giteaEventCatalog,
70
71
  adapters: {
71
72
  source_control: new GiteaSourceControlProvider(gitea),
72
73
  },
@@ -40,6 +40,8 @@ function giteaClient(overrides: Partial<GiteaApiClient> = {}): GiteaApiClient {
40
40
  listOrgRepositories: vi.fn(() => Promise.reject(new Error('not used'))),
41
41
  getRepository: vi.fn(() => Promise.reject(new Error('not used'))),
42
42
  resolveRef: vi.fn(() => Promise.reject(new Error('not used'))),
43
+ getBranch: vi.fn(() => Promise.reject(new Error('not used'))),
44
+ getTag: vi.fn(() => Promise.reject(new Error('not used'))),
43
45
  listTree: vi.fn(() => Promise.reject(new Error('not used'))),
44
46
  fetchFileContent: vi.fn(() => Promise.reject(new Error('not used'))),
45
47
  organizationExists: vi.fn(() => Promise.resolve(true)),