@shipfox/api-integration-github 11.0.0 → 12.2.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/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +34 -0
- package/dist/api/shared-installation-token-cache.d.ts.map +1 -1
- package/dist/core/agent-tools.d.ts.map +1 -1
- package/dist/core/errors.d.ts.map +1 -1
- package/dist/core/source-control.d.ts +2 -1
- package/dist/core/source-control.d.ts.map +1 -1
- package/dist/core/source-control.js +56 -1
- package/dist/core/source-control.js.map +1 -1
- package/dist/core/webhook-processor.d.ts +2 -1
- package/dist/core/webhook-processor.d.ts.map +1 -1
- package/dist/core/webhook-processor.js +1 -0
- package/dist/core/webhook-processor.js.map +1 -1
- package/dist/core/webhook.d.ts +3 -2
- package/dist/core/webhook.d.ts.map +1 -1
- package/dist/core/webhook.js +103 -1
- package/dist/core/webhook.js.map +1 -1
- package/dist/db/db.d.ts +2 -2
- package/dist/db/schema/installations.d.ts +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/presentation/routes/webhooks.d.ts +2 -1
- package/dist/presentation/routes/webhooks.d.ts.map +1 -1
- package/dist/presentation/routes/webhooks.js.map +1 -1
- package/dist/tsconfig.test.tsbuildinfo +1 -1
- package/package.json +8 -8
- package/src/connection-external-url.test.ts +1 -0
- package/src/core/agent-tools.test.ts +1 -0
- package/src/core/source-control.test.ts +80 -0
- package/src/core/source-control.ts +72 -0
- package/src/core/webhook-processor.test.ts +4 -0
- package/src/core/webhook-processor.ts +3 -0
- package/src/core/webhook.test.ts +197 -2
- package/src/core/webhook.ts +150 -0
- package/src/index.test.ts +1 -0
- package/src/index.ts +3 -0
- package/src/presentation/routes/install.test.ts +2 -1
- package/src/presentation/routes/webhooks.test.ts +49 -1
- package/src/presentation/routes/webhooks.ts +2 -0
- package/tsconfig.build.tsbuildinfo +1 -1
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shipfox/api-integration-github",
|
|
3
3
|
"license": "MIT",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "12.2.0",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "git+https://github.com/ShipfoxHQ/shipfox.git",
|
|
@@ -27,14 +27,14 @@
|
|
|
27
27
|
"ky": "^2.0.0",
|
|
28
28
|
"octokit": "^5.0.3",
|
|
29
29
|
"zod": "^4.4.3",
|
|
30
|
-
"@shipfox/api-auth-context": "
|
|
31
|
-
"@shipfox/api-integration-spi": "0.
|
|
32
|
-
"@shipfox/api-integration-github-dto": "
|
|
30
|
+
"@shipfox/api-auth-context": "12.2.0",
|
|
31
|
+
"@shipfox/api-integration-spi": "1.0.1",
|
|
32
|
+
"@shipfox/api-integration-github-dto": "12.2.0",
|
|
33
33
|
"@shipfox/config": "1.2.4",
|
|
34
|
-
"@shipfox/node-drizzle": "0.3.
|
|
35
|
-
"@shipfox/node-fastify": "0.4.
|
|
36
|
-
"@shipfox/node-postgres": "0.
|
|
37
|
-
"@shipfox/node-opentelemetry": "0.6.
|
|
34
|
+
"@shipfox/node-drizzle": "0.3.5",
|
|
35
|
+
"@shipfox/node-fastify": "0.4.2",
|
|
36
|
+
"@shipfox/node-postgres": "0.5.0",
|
|
37
|
+
"@shipfox/node-opentelemetry": "0.6.4",
|
|
38
38
|
"@shipfox/node-error-monitoring": "0.3.0"
|
|
39
39
|
},
|
|
40
40
|
"imports": {
|
|
@@ -40,6 +40,7 @@ function createProvider(lookup: (connectionId: string) => Promise<GithubInstalla
|
|
|
40
40
|
connectGithubInstallation: vi.fn() as never,
|
|
41
41
|
coreDb: vi.fn() as never,
|
|
42
42
|
publishIntegrationEventReceived: vi.fn(() => Promise.resolve({published: false})),
|
|
43
|
+
publishSourceRepositoryUpdated: vi.fn(() => Promise.resolve({published: false})),
|
|
43
44
|
publishSourcePush: vi.fn(() => Promise.resolve({published: false})),
|
|
44
45
|
recordDeliveryOnly: vi.fn(() => Promise.resolve()),
|
|
45
46
|
getIntegrationConnectionById: vi.fn(() => Promise.resolve(undefined)),
|
|
@@ -547,6 +547,7 @@ function createProvider() {
|
|
|
547
547
|
connectGithubInstallation: vi.fn() as never,
|
|
548
548
|
coreDb: vi.fn() as never,
|
|
549
549
|
publishIntegrationEventReceived: vi.fn(() => Promise.resolve({published: false})),
|
|
550
|
+
publishSourceRepositoryUpdated: vi.fn(() => Promise.resolve({published: false})),
|
|
550
551
|
publishSourcePush: vi.fn(() => Promise.resolve({published: false})),
|
|
551
552
|
recordDeliveryOnly: vi.fn(() => Promise.resolve()),
|
|
552
553
|
getIntegrationConnectionById: vi.fn(() => Promise.resolve(undefined)),
|
|
@@ -3,6 +3,8 @@ import {githubInstallationFactory} from '#test/index.js';
|
|
|
3
3
|
import {GithubIntegrationProviderError} from './errors.js';
|
|
4
4
|
import {GithubSourceControlProvider} from './source-control.js';
|
|
5
5
|
|
|
6
|
+
const VALID_COMMIT = 'a'.repeat(40);
|
|
7
|
+
|
|
6
8
|
function githubClient(overrides: Partial<GithubApiClient> = {}): GithubApiClient {
|
|
7
9
|
return {
|
|
8
10
|
exchangeOAuthCode: vi.fn(() => Promise.resolve('token')),
|
|
@@ -95,6 +97,84 @@ describe('GithubSourceControlProvider', () => {
|
|
|
95
97
|
};
|
|
96
98
|
}
|
|
97
99
|
|
|
100
|
+
it('normalizes push trigger references', () => {
|
|
101
|
+
const provider = new GithubSourceControlProvider(githubClient());
|
|
102
|
+
|
|
103
|
+
const result = provider.resolveTriggerReference({
|
|
104
|
+
ref: 'refs/heads/feature/review',
|
|
105
|
+
after: VALID_COMMIT,
|
|
106
|
+
repository: {id: 42},
|
|
107
|
+
sender: {login: 'octocat'},
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
expect(result).toEqual({
|
|
111
|
+
externalRepositoryId: 'github:42',
|
|
112
|
+
ref: 'refs/heads/feature/review',
|
|
113
|
+
commit: VALID_COMMIT,
|
|
114
|
+
actor: 'octocat',
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('resolves a null actor when the payload names no sender', () => {
|
|
119
|
+
const provider = new GithubSourceControlProvider(githubClient());
|
|
120
|
+
|
|
121
|
+
const result = provider.resolveTriggerReference({
|
|
122
|
+
ref: 'refs/heads/main',
|
|
123
|
+
after: VALID_COMMIT,
|
|
124
|
+
repository: {id: 42},
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
expect(result).toMatchObject({actor: null});
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('normalizes pull-request trigger references from the head repository', () => {
|
|
131
|
+
const provider = new GithubSourceControlProvider(githubClient());
|
|
132
|
+
|
|
133
|
+
const result = provider.resolveTriggerReference({
|
|
134
|
+
repository: {id: 42},
|
|
135
|
+
sender: {login: 'octocat'},
|
|
136
|
+
pull_request: {
|
|
137
|
+
number: 17,
|
|
138
|
+
head: {
|
|
139
|
+
sha: VALID_COMMIT,
|
|
140
|
+
repo: {id: 42},
|
|
141
|
+
},
|
|
142
|
+
base: {repo: {id: 42}},
|
|
143
|
+
},
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
expect(result).toEqual({
|
|
147
|
+
externalRepositoryId: 'github:42',
|
|
148
|
+
ref: 'refs/pull/17/head',
|
|
149
|
+
commit: VALID_COMMIT,
|
|
150
|
+
actor: 'octocat',
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it.each([
|
|
155
|
+
undefined,
|
|
156
|
+
{},
|
|
157
|
+
{ref: 'refs/heads/main', after: VALID_COMMIT},
|
|
158
|
+
{ref: 'refs/heads/feature branch', after: VALID_COMMIT, repository: {id: 42}},
|
|
159
|
+
{ref: 'refs/heads/feature..branch', after: VALID_COMMIT, repository: {id: 42}},
|
|
160
|
+
{ref: 'refs/heads/main', after: '0'.repeat(40), repository: {id: 42}},
|
|
161
|
+
{ref: 'refs/heads/main', after: 'not-a-commit', repository: {id: 42}},
|
|
162
|
+
{
|
|
163
|
+
repository: {id: 42},
|
|
164
|
+
pull_request: {
|
|
165
|
+
number: 17,
|
|
166
|
+
head: {sha: VALID_COMMIT, repo: {id: 84}},
|
|
167
|
+
base: {repo: {id: 42}},
|
|
168
|
+
},
|
|
169
|
+
},
|
|
170
|
+
])('returns null for an unresolvable or unsafe trigger payload', (payload) => {
|
|
171
|
+
const provider = new GithubSourceControlProvider(githubClient());
|
|
172
|
+
|
|
173
|
+
const result = provider.resolveTriggerReference(payload);
|
|
174
|
+
|
|
175
|
+
expect(result).toBeNull();
|
|
176
|
+
});
|
|
177
|
+
|
|
98
178
|
it('lists repositories using installation auth metadata', async () => {
|
|
99
179
|
await createInstallation();
|
|
100
180
|
const github = githubClient();
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {Buffer} from 'node:buffer';
|
|
2
2
|
import {
|
|
3
|
+
asRecord,
|
|
3
4
|
buildProviderRepositoryId,
|
|
4
5
|
type CheckoutSpec,
|
|
5
6
|
type CreateCheckoutSpecInput,
|
|
@@ -7,15 +8,21 @@ import {
|
|
|
7
8
|
type FilePage,
|
|
8
9
|
type FileSnapshot,
|
|
9
10
|
type IntegrationConnection,
|
|
11
|
+
isRecord,
|
|
12
|
+
isValidGitObjectId,
|
|
13
|
+
isValidTriggerRef,
|
|
10
14
|
type ListFilesInput,
|
|
11
15
|
type ListRepositoriesInput,
|
|
12
16
|
MAX_REPOSITORY_FILE_BYTES,
|
|
17
|
+
nonEmptyString,
|
|
13
18
|
parseProviderRepositoryId,
|
|
19
|
+
positiveInteger,
|
|
14
20
|
type RepositoryPage,
|
|
15
21
|
type RepositorySnapshot,
|
|
16
22
|
type RepositoryVisibility,
|
|
17
23
|
type ResolveRepositoryInput,
|
|
18
24
|
type SourceControlProvider,
|
|
25
|
+
type TriggerReference,
|
|
19
26
|
} from '@shipfox/api-integration-spi';
|
|
20
27
|
import type {GithubApiClient, GithubRepository} from '#api/client.js';
|
|
21
28
|
import {config} from '#config.js';
|
|
@@ -143,6 +150,48 @@ export class GithubSourceControlProvider
|
|
|
143
150
|
};
|
|
144
151
|
}
|
|
145
152
|
|
|
153
|
+
resolveTriggerReference(payload: unknown): TriggerReference | null {
|
|
154
|
+
if (!isRecord(payload)) return null;
|
|
155
|
+
|
|
156
|
+
const actor = githubEventActor(payload);
|
|
157
|
+
const pullRequest = asRecord(payload.pull_request);
|
|
158
|
+
if (pullRequest) {
|
|
159
|
+
const head = asRecord(pullRequest.head);
|
|
160
|
+
const repository = asRecord(head?.repo);
|
|
161
|
+
const base = asRecord(pullRequest.base);
|
|
162
|
+
const baseRepository = asRecord(base?.repo) ?? asRecord(payload.repository);
|
|
163
|
+
if (!repository || !baseRepository || !sameGithubRepository(repository, baseRepository)) {
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
const repositoryId = githubRepositoryId(repository);
|
|
167
|
+
const number = positiveInteger(pullRequest.number);
|
|
168
|
+
const commit = nonEmptyString(head?.sha);
|
|
169
|
+
const ref = number === null ? null : `refs/pull/${number}/head`;
|
|
170
|
+
if (!repositoryId || !ref || !commit) return null;
|
|
171
|
+
const hasValidReference = isValidGitObjectId(commit) && isValidTriggerRef(ref);
|
|
172
|
+
if (!hasValidReference) return null;
|
|
173
|
+
return {
|
|
174
|
+
externalRepositoryId: buildProviderRepositoryId(GITHUB_PROVIDER, repositoryId),
|
|
175
|
+
ref,
|
|
176
|
+
commit,
|
|
177
|
+
actor,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const repositoryId = githubRepositoryId(asRecord(payload.repository));
|
|
182
|
+
const ref = nonEmptyString(payload.ref);
|
|
183
|
+
const commit = nonEmptyString(payload.after);
|
|
184
|
+
if (!repositoryId || !ref || !commit) return null;
|
|
185
|
+
const hasValidReference = isValidGitObjectId(commit) && isValidTriggerRef(ref);
|
|
186
|
+
if (!hasValidReference) return null;
|
|
187
|
+
return {
|
|
188
|
+
externalRepositoryId: buildProviderRepositoryId(GITHUB_PROVIDER, repositoryId),
|
|
189
|
+
ref,
|
|
190
|
+
commit,
|
|
191
|
+
actor,
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
146
195
|
async createCheckoutSpec(
|
|
147
196
|
input: CreateCheckoutSpecInput<GithubIntegrationConnection>,
|
|
148
197
|
): Promise<CheckoutSpec> {
|
|
@@ -178,6 +227,29 @@ export class GithubSourceControlProvider
|
|
|
178
227
|
}
|
|
179
228
|
}
|
|
180
229
|
|
|
230
|
+
function githubRepositoryId(repository: Record<string, unknown> | null): string | null {
|
|
231
|
+
return positiveInteger(repository?.id) === null ? null : String(repository?.id);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// `sender` is the one actor field every GitHub webhook event carries, so it stays correct
|
|
235
|
+
// for pushes, pull requests, and anything else that reaches a trigger reference.
|
|
236
|
+
function githubEventActor(payload: Record<string, unknown>): string | null {
|
|
237
|
+
return nonEmptyString(asRecord(payload.sender)?.login);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function sameGithubRepository(
|
|
241
|
+
first: Record<string, unknown>,
|
|
242
|
+
second: Record<string, unknown>,
|
|
243
|
+
): boolean {
|
|
244
|
+
const firstId = githubRepositoryId(first);
|
|
245
|
+
const secondId = githubRepositoryId(second);
|
|
246
|
+
if (firstId && secondId) return firstId === secondId;
|
|
247
|
+
|
|
248
|
+
const firstName = nonEmptyString(first.full_name);
|
|
249
|
+
const secondName = nonEmptyString(second.full_name);
|
|
250
|
+
return Boolean(firstName && secondName && firstName.toLowerCase() === secondName.toLowerCase());
|
|
251
|
+
}
|
|
252
|
+
|
|
181
253
|
function githubAppGitAuthor(): CheckoutSpec['gitAuthor'] {
|
|
182
254
|
const appUsername = config.GITHUB_APP_USERNAME?.trim();
|
|
183
255
|
if (!appUsername) return undefined;
|
|
@@ -51,6 +51,7 @@ describe('GitHub webhook processor', () => {
|
|
|
51
51
|
const processor = createGithubWebhookProcessor({
|
|
52
52
|
coreDb,
|
|
53
53
|
publishIntegrationEventReceived: vi.fn(),
|
|
54
|
+
publishSourceRepositoryUpdated: vi.fn(),
|
|
54
55
|
publishSourcePush: vi.fn(),
|
|
55
56
|
recordDeliveryOnly: vi.fn(),
|
|
56
57
|
getIntegrationConnectionById: vi.fn(),
|
|
@@ -78,6 +79,7 @@ describe('GitHub webhook processor', () => {
|
|
|
78
79
|
const processor = createGithubWebhookProcessor({
|
|
79
80
|
coreDb: vi.fn(),
|
|
80
81
|
publishIntegrationEventReceived: vi.fn(),
|
|
82
|
+
publishSourceRepositoryUpdated: vi.fn(),
|
|
81
83
|
publishSourcePush: vi.fn(),
|
|
82
84
|
recordDeliveryOnly: vi.fn(),
|
|
83
85
|
getIntegrationConnectionById: vi.fn(),
|
|
@@ -114,6 +116,7 @@ describe('GitHub webhook processor', () => {
|
|
|
114
116
|
coreDb: () =>
|
|
115
117
|
({transaction: (callback: (tx: unknown) => Promise<unknown>) => callback({})}) as never,
|
|
116
118
|
publishIntegrationEventReceived: vi.fn(),
|
|
119
|
+
publishSourceRepositoryUpdated: vi.fn(),
|
|
117
120
|
publishSourcePush: vi.fn(),
|
|
118
121
|
recordDeliveryOnly: vi.fn(),
|
|
119
122
|
getIntegrationConnectionById: vi.fn(),
|
|
@@ -145,6 +148,7 @@ describe('GitHub webhook processor', () => {
|
|
|
145
148
|
const processor = createGithubWebhookProcessor({
|
|
146
149
|
coreDb: db,
|
|
147
150
|
publishIntegrationEventReceived,
|
|
151
|
+
publishSourceRepositoryUpdated: vi.fn(),
|
|
148
152
|
publishSourcePush: vi.fn(),
|
|
149
153
|
recordDeliveryOnly: vi.fn(),
|
|
150
154
|
getIntegrationConnectionById: vi.fn(() => Promise.resolve(connection)),
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
type GetIntegrationConnectionByIdFn,
|
|
6
6
|
type PublishIntegrationEventReceivedFn,
|
|
7
7
|
type PublishSourcePushFn,
|
|
8
|
+
type PublishSourceRepositoryUpdatedFn,
|
|
8
9
|
type RecordDeliveryOnlyFn,
|
|
9
10
|
type StoredWebhookRequest,
|
|
10
11
|
type WebhookProcessingResult,
|
|
@@ -21,6 +22,7 @@ const SIGNATURE_HEADER = 'x-hub-signature-256';
|
|
|
21
22
|
export interface CreateGithubWebhookProcessorOptions {
|
|
22
23
|
coreDb: () => NodePgDatabase<Record<string, unknown>>;
|
|
23
24
|
publishIntegrationEventReceived: PublishIntegrationEventReceivedFn;
|
|
25
|
+
publishSourceRepositoryUpdated: PublishSourceRepositoryUpdatedFn;
|
|
24
26
|
publishSourcePush: PublishSourcePushFn;
|
|
25
27
|
recordDeliveryOnly: RecordDeliveryOnlyFn;
|
|
26
28
|
getIntegrationConnectionById: GetIntegrationConnectionByIdFn;
|
|
@@ -81,6 +83,7 @@ async function processGithubWebhookRequest(
|
|
|
81
83
|
event,
|
|
82
84
|
payload,
|
|
83
85
|
publishIntegrationEventReceived: options.publishIntegrationEventReceived,
|
|
86
|
+
publishSourceRepositoryUpdated: options.publishSourceRepositoryUpdated,
|
|
84
87
|
publishSourcePush: options.publishSourcePush,
|
|
85
88
|
recordDeliveryOnly: options.recordDeliveryOnly,
|
|
86
89
|
getIntegrationConnectionById: options.getIntegrationConnectionById,
|
package/src/core/webhook.test.ts
CHANGED
|
@@ -4,6 +4,7 @@ import type {
|
|
|
4
4
|
IntegrationConnection,
|
|
5
5
|
PublishIntegrationEventReceivedFn,
|
|
6
6
|
PublishSourcePushFn,
|
|
7
|
+
PublishSourceRepositoryUpdatedFn,
|
|
7
8
|
RecordDeliveryOnlyFn,
|
|
8
9
|
} from '@shipfox/api-integration-spi';
|
|
9
10
|
import {db} from '#db/db.js';
|
|
@@ -30,6 +31,7 @@ function deps(
|
|
|
30
31
|
options: {
|
|
31
32
|
connection?: IntegrationConnection | undefined;
|
|
32
33
|
publishIntegrationEventReceivedResult?: {published: boolean};
|
|
34
|
+
publishSourceRepositoryUpdatedResult?: {published: boolean};
|
|
33
35
|
publishSourcePushResult?: {published: boolean};
|
|
34
36
|
} = {},
|
|
35
37
|
) {
|
|
@@ -37,6 +39,9 @@ function deps(
|
|
|
37
39
|
publishIntegrationEventReceived: vi.fn<PublishIntegrationEventReceivedFn>(() =>
|
|
38
40
|
Promise.resolve(options.publishIntegrationEventReceivedResult ?? {published: true}),
|
|
39
41
|
),
|
|
42
|
+
publishSourceRepositoryUpdated: vi.fn<PublishSourceRepositoryUpdatedFn>(() =>
|
|
43
|
+
Promise.resolve(options.publishSourceRepositoryUpdatedResult ?? {published: true}),
|
|
44
|
+
),
|
|
40
45
|
publishSourcePush: vi.fn<PublishSourcePushFn>(() =>
|
|
41
46
|
Promise.resolve(options.publishSourcePushResult ?? {published: true}),
|
|
42
47
|
),
|
|
@@ -272,7 +277,12 @@ describe('handleGithubEvent', () => {
|
|
|
272
277
|
const payload = {
|
|
273
278
|
action: 'opened',
|
|
274
279
|
installation: {id: installationId},
|
|
275
|
-
|
|
280
|
+
repository: {id: 42, full_name: 'shipfox/platform'},
|
|
281
|
+
pull_request: {
|
|
282
|
+
number: 17,
|
|
283
|
+
head: {repo: {id: 42, full_name: 'shipfox/platform'}},
|
|
284
|
+
base: {repo: {id: 42, full_name: 'shipfox/platform'}},
|
|
285
|
+
},
|
|
276
286
|
};
|
|
277
287
|
|
|
278
288
|
const result = await handleGithubEvent({
|
|
@@ -299,6 +309,186 @@ describe('handleGithubEvent', () => {
|
|
|
299
309
|
});
|
|
300
310
|
});
|
|
301
311
|
|
|
312
|
+
it('records and drops pull request deliveries whose head repository is a fork', async () => {
|
|
313
|
+
const handlers = deps();
|
|
314
|
+
const deliveryId = randomUUID();
|
|
315
|
+
const payload = {
|
|
316
|
+
action: 'opened',
|
|
317
|
+
installation: {id: 7787},
|
|
318
|
+
repository: {id: 42, full_name: 'shipfox/platform'},
|
|
319
|
+
pull_request: {
|
|
320
|
+
head: {repo: {id: 84, full_name: 'contributor/platform'}},
|
|
321
|
+
base: {repo: {id: 42, full_name: 'shipfox/platform'}},
|
|
322
|
+
},
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
const result = await handleGithubEvent({
|
|
326
|
+
tx: db(),
|
|
327
|
+
deliveryId,
|
|
328
|
+
event: 'pull_request',
|
|
329
|
+
payload,
|
|
330
|
+
...handlers,
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
expect(result.outcome).toBe('fork-pull-request');
|
|
334
|
+
expect(handlers.publishSourcePush).not.toHaveBeenCalled();
|
|
335
|
+
expect(handlers.publishIntegrationEventReceived).not.toHaveBeenCalled();
|
|
336
|
+
expect(handlers.getIntegrationConnectionById).not.toHaveBeenCalled();
|
|
337
|
+
expect(firstRecordDeliveryOnlyCall(handlers.recordDeliveryOnly)).toMatchObject({
|
|
338
|
+
provider: 'github',
|
|
339
|
+
deliveryId,
|
|
340
|
+
});
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
it('records and drops pull request deliveries whose head repository is unresolved', async () => {
|
|
344
|
+
const handlers = deps();
|
|
345
|
+
const deliveryId = randomUUID();
|
|
346
|
+
const payload = {
|
|
347
|
+
action: 'synchronize',
|
|
348
|
+
installation: {id: 7789},
|
|
349
|
+
repository: {id: 42, full_name: 'shipfox/platform'},
|
|
350
|
+
pull_request: {
|
|
351
|
+
head: {repo: null},
|
|
352
|
+
base: {repo: {id: 42, full_name: 'shipfox/platform'}},
|
|
353
|
+
},
|
|
354
|
+
};
|
|
355
|
+
|
|
356
|
+
const result = await handleGithubEvent({
|
|
357
|
+
tx: db(),
|
|
358
|
+
deliveryId,
|
|
359
|
+
event: 'pull_request',
|
|
360
|
+
payload,
|
|
361
|
+
...handlers,
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
expect(result.outcome).toBe('fork-pull-request');
|
|
365
|
+
expect(handlers.publishSourcePush).not.toHaveBeenCalled();
|
|
366
|
+
expect(handlers.publishIntegrationEventReceived).not.toHaveBeenCalled();
|
|
367
|
+
expect(handlers.getIntegrationConnectionById).not.toHaveBeenCalled();
|
|
368
|
+
expect(firstRecordDeliveryOnlyCall(handlers.recordDeliveryOnly)).toMatchObject({
|
|
369
|
+
provider: 'github',
|
|
370
|
+
deliveryId,
|
|
371
|
+
});
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
it('publishes pull request deliveries whose head and base repositories match', async () => {
|
|
375
|
+
const installationId = 7788;
|
|
376
|
+
const connection = fakeConnection();
|
|
377
|
+
await seedInstallation(installationId, connection.id);
|
|
378
|
+
const handlers = deps({connection});
|
|
379
|
+
const payload = {
|
|
380
|
+
action: 'opened',
|
|
381
|
+
installation: {id: installationId},
|
|
382
|
+
repository: {id: 42, full_name: 'shipfox/platform'},
|
|
383
|
+
pull_request: {
|
|
384
|
+
head: {repo: {id: 42, full_name: 'shipfox/platform'}},
|
|
385
|
+
base: {repo: {id: 42, full_name: 'shipfox/platform'}},
|
|
386
|
+
},
|
|
387
|
+
};
|
|
388
|
+
|
|
389
|
+
const result = await handleGithubEvent({
|
|
390
|
+
tx: db(),
|
|
391
|
+
deliveryId: randomUUID(),
|
|
392
|
+
event: 'pull_request',
|
|
393
|
+
payload,
|
|
394
|
+
...handlers,
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
expect(result.outcome).toBe('published-envelope');
|
|
398
|
+
expect(handlers.recordDeliveryOnly).not.toHaveBeenCalled();
|
|
399
|
+
expect(handlers.publishIntegrationEventReceived).toHaveBeenCalledTimes(1);
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
it('publishes a typed repository update for a repository rename', async () => {
|
|
403
|
+
const installationId = 7784;
|
|
404
|
+
const connection = fakeConnection();
|
|
405
|
+
await seedInstallation(installationId, connection.id);
|
|
406
|
+
const handlers = deps({connection});
|
|
407
|
+
const deliveryId = randomUUID();
|
|
408
|
+
const payload = {
|
|
409
|
+
action: 'renamed',
|
|
410
|
+
installation: {id: installationId},
|
|
411
|
+
repository: {
|
|
412
|
+
id: 42,
|
|
413
|
+
name: 'platform-renamed',
|
|
414
|
+
owner: {login: 'acme'},
|
|
415
|
+
default_branch: 'trunk',
|
|
416
|
+
},
|
|
417
|
+
};
|
|
418
|
+
|
|
419
|
+
const result = await handleGithubEvent({
|
|
420
|
+
tx: db(),
|
|
421
|
+
deliveryId,
|
|
422
|
+
event: 'repository',
|
|
423
|
+
payload,
|
|
424
|
+
...handlers,
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
expect(result.outcome).toBe('published');
|
|
428
|
+
expect(handlers.publishIntegrationEventReceived).not.toHaveBeenCalled();
|
|
429
|
+
expect(handlers.publishSourceRepositoryUpdated).toHaveBeenCalledWith(
|
|
430
|
+
expect.objectContaining({
|
|
431
|
+
event: 'repository.renamed',
|
|
432
|
+
rawPayload: payload,
|
|
433
|
+
repositories: [
|
|
434
|
+
{
|
|
435
|
+
externalRepositoryId: 'github:42',
|
|
436
|
+
owner: 'acme',
|
|
437
|
+
name: 'platform-renamed',
|
|
438
|
+
defaultBranch: 'trunk',
|
|
439
|
+
},
|
|
440
|
+
],
|
|
441
|
+
}),
|
|
442
|
+
);
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
it('publishes one typed repository update per installation repository', async () => {
|
|
446
|
+
const installationId = 7785;
|
|
447
|
+
const connection = fakeConnection();
|
|
448
|
+
await seedInstallation(installationId, connection.id);
|
|
449
|
+
const handlers = deps({connection});
|
|
450
|
+
const deliveryId = randomUUID();
|
|
451
|
+
const payload = {
|
|
452
|
+
action: 'added',
|
|
453
|
+
installation: {id: installationId},
|
|
454
|
+
repositories_added: [
|
|
455
|
+
{id: 42, name: 'platform', owner: {login: 'acme'}, default_branch: 'main'},
|
|
456
|
+
],
|
|
457
|
+
repositories_removed: [
|
|
458
|
+
{id: 43, name: 'runner', owner: {login: 'acme'}, default_branch: 'trunk'},
|
|
459
|
+
],
|
|
460
|
+
};
|
|
461
|
+
|
|
462
|
+
const result = await handleGithubEvent({
|
|
463
|
+
tx: db(),
|
|
464
|
+
deliveryId,
|
|
465
|
+
event: 'installation_repositories',
|
|
466
|
+
payload,
|
|
467
|
+
...handlers,
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
expect(result.outcome).toBe('published');
|
|
471
|
+
expect(handlers.publishSourceRepositoryUpdated).toHaveBeenCalledWith(
|
|
472
|
+
expect.objectContaining({
|
|
473
|
+
event: 'installation_repositories.added',
|
|
474
|
+
repositories: [
|
|
475
|
+
{
|
|
476
|
+
externalRepositoryId: 'github:42',
|
|
477
|
+
owner: 'acme',
|
|
478
|
+
name: 'platform',
|
|
479
|
+
defaultBranch: 'main',
|
|
480
|
+
},
|
|
481
|
+
{
|
|
482
|
+
externalRepositoryId: 'github:43',
|
|
483
|
+
owner: 'acme',
|
|
484
|
+
name: 'runner',
|
|
485
|
+
defaultBranch: 'trunk',
|
|
486
|
+
},
|
|
487
|
+
],
|
|
488
|
+
}),
|
|
489
|
+
);
|
|
490
|
+
});
|
|
491
|
+
|
|
302
492
|
it('returns the cached installation token cleanup handle when the installation is deleted', async () => {
|
|
303
493
|
const installationId = 7791;
|
|
304
494
|
const connection = fakeConnection();
|
|
@@ -374,7 +564,12 @@ describe('handleGithubEvent', () => {
|
|
|
374
564
|
const payload = {
|
|
375
565
|
action: null,
|
|
376
566
|
installation: {id: installationId},
|
|
377
|
-
|
|
567
|
+
repository: {id: 42, full_name: 'shipfox/platform'},
|
|
568
|
+
pull_request: {
|
|
569
|
+
number: 17,
|
|
570
|
+
head: {repo: {id: 42, full_name: 'shipfox/platform'}},
|
|
571
|
+
base: {repo: {id: 42, full_name: 'shipfox/platform'}},
|
|
572
|
+
},
|
|
378
573
|
};
|
|
379
574
|
|
|
380
575
|
const result = await handleGithubEvent({
|