@shipfox/api-integration-core 2.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/.turbo/turbo-build.log +2 -0
- package/.turbo/turbo-type$colon$emit.log +1 -0
- package/.turbo/turbo-type.log +1 -0
- package/CHANGELOG.md +264 -0
- package/LICENSE +21 -0
- package/dist/config.d.ts +9 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +29 -0
- package/dist/config.js.map +1 -0
- package/dist/core/agent-tool-selection.d.ts +15 -0
- package/dist/core/agent-tool-selection.d.ts.map +1 -0
- package/dist/core/agent-tool-selection.js +48 -0
- package/dist/core/agent-tool-selection.js.map +1 -0
- package/dist/core/entities/connection.d.ts +2 -0
- package/dist/core/entities/connection.d.ts.map +1 -0
- package/dist/core/entities/connection.js +3 -0
- package/dist/core/entities/connection.js.map +1 -0
- package/dist/core/entities/provider.d.ts +6 -0
- package/dist/core/entities/provider.d.ts.map +1 -0
- package/dist/core/entities/provider.js +3 -0
- package/dist/core/entities/provider.js.map +1 -0
- package/dist/core/errors.d.ts +29 -0
- package/dist/core/errors.d.ts.map +1 -0
- package/dist/core/errors.js +40 -0
- package/dist/core/errors.js.map +1 -0
- package/dist/core/providers/agent-tools.d.ts +2 -0
- package/dist/core/providers/agent-tools.d.ts.map +1 -0
- package/dist/core/providers/agent-tools.js +3 -0
- package/dist/core/providers/agent-tools.js.map +1 -0
- package/dist/core/providers/redact-checkout-spec.d.ts +9 -0
- package/dist/core/providers/redact-checkout-spec.d.ts.map +1 -0
- package/dist/core/providers/redact-checkout-spec.js +25 -0
- package/dist/core/providers/redact-checkout-spec.js.map +1 -0
- package/dist/core/providers/registry.d.ts +9 -0
- package/dist/core/providers/registry.d.ts.map +1 -0
- package/dist/core/providers/registry.js +52 -0
- package/dist/core/providers/registry.js.map +1 -0
- package/dist/core/providers/source-control.d.ts +2 -0
- package/dist/core/providers/source-control.d.ts.map +1 -0
- package/dist/core/providers/source-control.js +3 -0
- package/dist/core/providers/source-control.js.map +1 -0
- package/dist/core/source-control-service.d.ts +46 -0
- package/dist/core/source-control-service.d.ts.map +1 -0
- package/dist/core/source-control-service.js +84 -0
- package/dist/core/source-control-service.js.map +1 -0
- package/dist/db/connections.d.ts +67 -0
- package/dist/db/connections.d.ts.map +1 -0
- package/dist/db/connections.js +130 -0
- package/dist/db/connections.js.map +1 -0
- package/dist/db/db.d.ts +865 -0
- package/dist/db/db.d.ts.map +1 -0
- package/dist/db/db.js +22 -0
- package/dist/db/db.js.map +1 -0
- package/dist/db/migrations.d.ts +2 -0
- package/dist/db/migrations.d.ts.map +1 -0
- package/dist/db/migrations.js +5 -0
- package/dist/db/migrations.js.map +1 -0
- package/dist/db/schema/common.d.ts +2 -0
- package/dist/db/schema/common.d.ts.map +1 -0
- package/dist/db/schema/common.js +4 -0
- package/dist/db/schema/common.js.map +1 -0
- package/dist/db/schema/connections.d.ts +165 -0
- package/dist/db/schema/connections.d.ts.map +1 -0
- package/dist/db/schema/connections.js +37 -0
- package/dist/db/schema/connections.js.map +1 -0
- package/dist/db/schema/outbox.d.ts +195 -0
- package/dist/db/schema/outbox.d.ts.map +1 -0
- package/dist/db/schema/outbox.js +5 -0
- package/dist/db/schema/outbox.js.map +1 -0
- package/dist/db/schema/webhook-deliveries.d.ts +76 -0
- package/dist/db/schema/webhook-deliveries.d.ts.map +1 -0
- package/dist/db/schema/webhook-deliveries.js +22 -0
- package/dist/db/schema/webhook-deliveries.js.map +1 -0
- package/dist/db/webhook-deliveries.d.ts +55 -0
- package/dist/db/webhook-deliveries.d.ts.map +1 -0
- package/dist/db/webhook-deliveries.js +132 -0
- package/dist/db/webhook-deliveries.js.map +1 -0
- package/dist/index.d.ts +54 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +105 -0
- package/dist/index.js.map +1 -0
- package/dist/metrics/index.d.ts +2 -0
- package/dist/metrics/index.d.ts.map +1 -0
- package/dist/metrics/index.js +3 -0
- package/dist/metrics/index.js.map +1 -0
- package/dist/metrics/instance.d.ts +8 -0
- package/dist/metrics/instance.d.ts.map +1 -0
- package/dist/metrics/instance.js +17 -0
- package/dist/metrics/instance.js.map +1 -0
- package/dist/presentation/dto/integrations.d.ts +36 -0
- package/dist/presentation/dto/integrations.d.ts.map +1 -0
- package/dist/presentation/dto/integrations.js +26 -0
- package/dist/presentation/dto/integrations.js.map +1 -0
- package/dist/presentation/routes/agent-tools-gateway/audit.d.ts +24 -0
- package/dist/presentation/routes/agent-tools-gateway/audit.d.ts.map +1 -0
- package/dist/presentation/routes/agent-tools-gateway/audit.js +52 -0
- package/dist/presentation/routes/agent-tools-gateway/audit.js.map +1 -0
- package/dist/presentation/routes/agent-tools-gateway/dispatch.d.ts +7 -0
- package/dist/presentation/routes/agent-tools-gateway/dispatch.d.ts.map +1 -0
- package/dist/presentation/routes/agent-tools-gateway/dispatch.js +108 -0
- package/dist/presentation/routes/agent-tools-gateway/dispatch.js.map +1 -0
- package/dist/presentation/routes/agent-tools-gateway/index.d.ts +12 -0
- package/dist/presentation/routes/agent-tools-gateway/index.d.ts.map +1 -0
- package/dist/presentation/routes/agent-tools-gateway/index.js +46 -0
- package/dist/presentation/routes/agent-tools-gateway/index.js.map +1 -0
- package/dist/presentation/routes/agent-tools-gateway/mcp-server.d.ts +17 -0
- package/dist/presentation/routes/agent-tools-gateway/mcp-server.d.ts.map +1 -0
- package/dist/presentation/routes/agent-tools-gateway/mcp-server.js +126 -0
- package/dist/presentation/routes/agent-tools-gateway/mcp-server.js.map +1 -0
- package/dist/presentation/routes/agent-tools-gateway/resolve-authorized-tools.d.ts +37 -0
- package/dist/presentation/routes/agent-tools-gateway/resolve-authorized-tools.d.ts.map +1 -0
- package/dist/presentation/routes/agent-tools-gateway/resolve-authorized-tools.js +145 -0
- package/dist/presentation/routes/agent-tools-gateway/resolve-authorized-tools.js.map +1 -0
- package/dist/presentation/routes/errors.d.ts +2 -0
- package/dist/presentation/routes/errors.d.ts.map +1 -0
- package/dist/presentation/routes/errors.js +53 -0
- package/dist/presentation/routes/errors.js.map +1 -0
- package/dist/presentation/routes/index.d.ts +14 -0
- package/dist/presentation/routes/index.d.ts.map +1 -0
- package/dist/presentation/routes/index.js +26 -0
- package/dist/presentation/routes/index.js.map +1 -0
- package/dist/presentation/routes/list-connections.d.ts +3 -0
- package/dist/presentation/routes/list-connections.d.ts.map +1 -0
- package/dist/presentation/routes/list-connections.js +58 -0
- package/dist/presentation/routes/list-connections.js.map +1 -0
- package/dist/presentation/routes/list-providers.d.ts +3 -0
- package/dist/presentation/routes/list-providers.d.ts.map +1 -0
- package/dist/presentation/routes/list-providers.js +27 -0
- package/dist/presentation/routes/list-providers.js.map +1 -0
- package/dist/presentation/routes/list-repositories.d.ts +3 -0
- package/dist/presentation/routes/list-repositories.d.ts.map +1 -0
- package/dist/presentation/routes/list-repositories.js +41 -0
- package/dist/presentation/routes/list-repositories.js.map +1 -0
- package/dist/presentation/routes/manage-connections.d.ts +4 -0
- package/dist/presentation/routes/manage-connections.d.ts.map +1 -0
- package/dist/presentation/routes/manage-connections.js +81 -0
- package/dist/presentation/routes/manage-connections.js.map +1 -0
- package/dist/providers/connection-slug.d.ts +2 -0
- package/dist/providers/connection-slug.d.ts.map +1 -0
- package/dist/providers/connection-slug.js +19 -0
- package/dist/providers/connection-slug.js.map +1 -0
- package/dist/providers/cron.d.ts +3 -0
- package/dist/providers/cron.d.ts.map +1 -0
- package/dist/providers/cron.js +13 -0
- package/dist/providers/cron.js.map +1 -0
- package/dist/providers/gitea.d.ts +3 -0
- package/dist/providers/gitea.d.ts.map +1 -0
- package/dist/providers/gitea.js +75 -0
- package/dist/providers/gitea.js.map +1 -0
- package/dist/providers/github.d.ts +3 -0
- package/dist/providers/github.d.ts.map +1 -0
- package/dist/providers/github.js +109 -0
- package/dist/providers/github.js.map +1 -0
- package/dist/providers/linear.d.ts +3 -0
- package/dist/providers/linear.d.ts.map +1 -0
- package/dist/providers/linear.js +134 -0
- package/dist/providers/linear.js.map +1 -0
- package/dist/providers/modules.d.ts +3 -0
- package/dist/providers/modules.d.ts.map +1 -0
- package/dist/providers/modules.js +26 -0
- package/dist/providers/modules.js.map +1 -0
- package/dist/providers/sentry.d.ts +3 -0
- package/dist/providers/sentry.d.ts.map +1 -0
- package/dist/providers/sentry.js +83 -0
- package/dist/providers/sentry.js.map +1 -0
- package/dist/providers/types.d.ts +58 -0
- package/dist/providers/types.d.ts.map +1 -0
- package/dist/providers/types.js +3 -0
- package/dist/providers/types.js.map +1 -0
- package/dist/providers/webhook.d.ts +3 -0
- package/dist/providers/webhook.d.ts.map +1 -0
- package/dist/providers/webhook.js +24 -0
- package/dist/providers/webhook.js.map +1 -0
- package/dist/temporal/activities/index.d.ts +5 -0
- package/dist/temporal/activities/index.d.ts.map +1 -0
- package/dist/temporal/activities/index.js +8 -0
- package/dist/temporal/activities/index.js.map +1 -0
- package/dist/temporal/activities/prune-webhook-deliveries.d.ts +4 -0
- package/dist/temporal/activities/prune-webhook-deliveries.d.ts.map +1 -0
- package/dist/temporal/activities/prune-webhook-deliveries.js +11 -0
- package/dist/temporal/activities/prune-webhook-deliveries.js.map +1 -0
- package/dist/temporal/constants.d.ts +3 -0
- package/dist/temporal/constants.d.ts.map +1 -0
- package/dist/temporal/constants.js +4 -0
- package/dist/temporal/constants.js.map +1 -0
- package/dist/temporal/workflows/index.d.ts +2 -0
- package/dist/temporal/workflows/index.d.ts.map +1 -0
- package/dist/temporal/workflows/index.js +3 -0
- package/dist/temporal/workflows/index.js.map +1 -0
- package/dist/temporal/workflows/prune-webhook-deliveries-cron.d.ts +2 -0
- package/dist/temporal/workflows/prune-webhook-deliveries-cron.d.ts.map +1 -0
- package/dist/temporal/workflows/prune-webhook-deliveries-cron.js +14 -0
- package/dist/temporal/workflows/prune-webhook-deliveries-cron.js.map +1 -0
- package/dist/tsconfig.test.tsbuildinfo +1 -0
- package/drizzle/0000_initial.sql +41 -0
- package/drizzle/meta/0001_snapshot.json +341 -0
- package/drizzle/meta/_journal.json +13 -0
- package/drizzle.config.ts +7 -0
- package/package.json +85 -0
- package/src/config.test.ts +32 -0
- package/src/config.ts +28 -0
- package/src/core/agent-tool-selection.test.ts +137 -0
- package/src/core/agent-tool-selection.ts +80 -0
- package/src/core/entities/connection.ts +4 -0
- package/src/core/entities/provider.ts +21 -0
- package/src/core/errors.ts +55 -0
- package/src/core/providers/agent-tools.ts +10 -0
- package/src/core/providers/redact-checkout-spec.test.ts +78 -0
- package/src/core/providers/redact-checkout-spec.ts +20 -0
- package/src/core/providers/registry.test.ts +138 -0
- package/src/core/providers/registry.ts +90 -0
- package/src/core/providers/source-control.ts +17 -0
- package/src/core/source-control-service.test.ts +230 -0
- package/src/core/source-control-service.ts +161 -0
- package/src/db/connections.test.ts +296 -0
- package/src/db/connections.ts +262 -0
- package/src/db/db.ts +22 -0
- package/src/db/migrations.ts +4 -0
- package/src/db/schema/common.ts +3 -0
- package/src/db/schema/connections.ts +49 -0
- package/src/db/schema/outbox.ts +4 -0
- package/src/db/schema/webhook-deliveries.ts +19 -0
- package/src/db/webhook-deliveries.test.ts +267 -0
- package/src/db/webhook-deliveries.ts +207 -0
- package/src/index.ts +212 -0
- package/src/metrics/index.ts +1 -0
- package/src/metrics/instance.ts +35 -0
- package/src/presentation/dto/integrations.ts +39 -0
- package/src/presentation/routes/agent-tools-gateway/agent-tools-gateway.route.test.ts +371 -0
- package/src/presentation/routes/agent-tools-gateway/audit.test.ts +86 -0
- package/src/presentation/routes/agent-tools-gateway/audit.ts +90 -0
- package/src/presentation/routes/agent-tools-gateway/dispatch.ts +139 -0
- package/src/presentation/routes/agent-tools-gateway/index.ts +62 -0
- package/src/presentation/routes/agent-tools-gateway/mcp-server.test.ts +308 -0
- package/src/presentation/routes/agent-tools-gateway/mcp-server.ts +155 -0
- package/src/presentation/routes/agent-tools-gateway/resolve-authorized-tools.test.ts +181 -0
- package/src/presentation/routes/agent-tools-gateway/resolve-authorized-tools.ts +245 -0
- package/src/presentation/routes/errors.ts +63 -0
- package/src/presentation/routes/index.ts +53 -0
- package/src/presentation/routes/list-connections.test.ts +227 -0
- package/src/presentation/routes/list-connections.ts +56 -0
- package/src/presentation/routes/list-providers.test.ts +113 -0
- package/src/presentation/routes/list-providers.ts +28 -0
- package/src/presentation/routes/list-repositories.test.ts +150 -0
- package/src/presentation/routes/list-repositories.ts +47 -0
- package/src/presentation/routes/manage-connections.test.ts +125 -0
- package/src/presentation/routes/manage-connections.ts +77 -0
- package/src/providers/connection-slug.ts +19 -0
- package/src/providers/cron.ts +13 -0
- package/src/providers/gitea.ts +103 -0
- package/src/providers/github.ts +150 -0
- package/src/providers/linear.ts +179 -0
- package/src/providers/modules.test.ts +40 -0
- package/src/providers/modules.ts +32 -0
- package/src/providers/sentry.ts +113 -0
- package/src/providers/types.ts +52 -0
- package/src/providers/webhook.ts +30 -0
- package/src/temporal/activities/index.ts +7 -0
- package/src/temporal/activities/prune-webhook-deliveries.test.ts +39 -0
- package/src/temporal/activities/prune-webhook-deliveries.ts +9 -0
- package/src/temporal/constants.ts +3 -0
- package/src/temporal/workflows/index.ts +1 -0
- package/src/temporal/workflows/prune-webhook-deliveries-cron.ts +15 -0
- package/test/agent-tools-gateway-helpers.ts +210 -0
- package/test/env.ts +23 -0
- package/test/factories/connection.ts +21 -0
- package/test/globalSetup.ts +22 -0
- package/test/index.ts +1 -0
- package/test/route-utils.ts +104 -0
- package/test/setup.ts +19 -0
- package/tsconfig.build.json +9 -0
- package/tsconfig.build.tsbuildinfo +1 -0
- package/tsconfig.json +3 -0
- package/tsconfig.test.json +8 -0
- package/vitest.config.ts +13 -0
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import {IntegrationCheckoutUnsupportedError, IntegrationProviderError} from './errors.js';
|
|
2
|
+
import {createIntegrationProviderRegistry} from './providers/registry.js';
|
|
3
|
+
import type {RepositorySnapshot, SourceControlProvider} from './providers/source-control.js';
|
|
4
|
+
import {createSourceControlIntegrationService} from './source-control-service.js';
|
|
5
|
+
|
|
6
|
+
const repository: RepositorySnapshot = {
|
|
7
|
+
externalRepositoryId: 'gitea:gitea-owner/platform',
|
|
8
|
+
owner: 'gitea-owner',
|
|
9
|
+
name: 'platform',
|
|
10
|
+
fullName: 'gitea-owner/platform',
|
|
11
|
+
defaultBranch: 'main',
|
|
12
|
+
visibility: 'private',
|
|
13
|
+
cloneUrl: 'https://gitea.local/gitea-owner/platform.git',
|
|
14
|
+
htmlUrl: 'https://gitea.local/gitea-owner/platform',
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
describe('integration source-control service', () => {
|
|
18
|
+
const workspaceId = crypto.randomUUID();
|
|
19
|
+
const connection = {
|
|
20
|
+
id: crypto.randomUUID(),
|
|
21
|
+
workspaceId,
|
|
22
|
+
provider: 'gitea' as const,
|
|
23
|
+
externalAccountId: 'gitea-owner',
|
|
24
|
+
slug: 'gitea_owner',
|
|
25
|
+
displayName: 'Gitea',
|
|
26
|
+
lifecycleStatus: 'active' as const,
|
|
27
|
+
createdAt: new Date(),
|
|
28
|
+
updatedAt: new Date(),
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
function createService(
|
|
32
|
+
overrides: Partial<SourceControlProvider> = {},
|
|
33
|
+
options: {omitCheckoutSpec?: boolean} = {},
|
|
34
|
+
) {
|
|
35
|
+
const sourceControl: SourceControlProvider = {
|
|
36
|
+
listRepositories: async () => {
|
|
37
|
+
await Promise.resolve();
|
|
38
|
+
return {repositories: [repository], nextCursor: null};
|
|
39
|
+
},
|
|
40
|
+
resolveRepository: async () => {
|
|
41
|
+
await Promise.resolve();
|
|
42
|
+
return repository;
|
|
43
|
+
},
|
|
44
|
+
listFiles: async () => {
|
|
45
|
+
await Promise.resolve();
|
|
46
|
+
return {
|
|
47
|
+
files: [{path: '.shipfox/workflows/ci.yml', type: 'file', size: 64}],
|
|
48
|
+
nextCursor: null,
|
|
49
|
+
};
|
|
50
|
+
},
|
|
51
|
+
fetchFile: async () => {
|
|
52
|
+
await Promise.resolve();
|
|
53
|
+
return {path: '.shipfox/workflows/ci.yml', ref: 'main', content: 'name: CI'};
|
|
54
|
+
},
|
|
55
|
+
createCheckoutSpec: async (input) => {
|
|
56
|
+
await Promise.resolve();
|
|
57
|
+
return {repositoryUrl: repository.cloneUrl, ref: input.ref ?? repository.defaultBranch};
|
|
58
|
+
},
|
|
59
|
+
...overrides,
|
|
60
|
+
};
|
|
61
|
+
if (options.omitCheckoutSpec) {
|
|
62
|
+
delete sourceControl.createCheckoutSpec;
|
|
63
|
+
}
|
|
64
|
+
return createSourceControlIntegrationService({
|
|
65
|
+
registry: createIntegrationProviderRegistry([
|
|
66
|
+
{
|
|
67
|
+
provider: 'gitea',
|
|
68
|
+
displayName: 'Gitea',
|
|
69
|
+
adapters: {
|
|
70
|
+
source_control: sourceControl,
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
]),
|
|
74
|
+
getIntegrationConnectionById: async (connectionId) => {
|
|
75
|
+
await Promise.resolve();
|
|
76
|
+
return connectionId === connection.id ? connection : undefined;
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
it('resolves a repository through an active source-control connection', async () => {
|
|
82
|
+
const service = createService();
|
|
83
|
+
|
|
84
|
+
const result = await service.resolveRepository({
|
|
85
|
+
workspaceId,
|
|
86
|
+
connectionId: connection.id,
|
|
87
|
+
externalRepositoryId: 'gitea:gitea-owner/platform',
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
expect(result.connection.id).toBe(connection.id);
|
|
91
|
+
expect(result.repository.externalRepositoryId).toBe('gitea:gitea-owner/platform');
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('rejects a missing connection', async () => {
|
|
95
|
+
const service = createService();
|
|
96
|
+
|
|
97
|
+
const result = service.resolveRepository({
|
|
98
|
+
workspaceId,
|
|
99
|
+
connectionId: crypto.randomUUID(),
|
|
100
|
+
externalRepositoryId: 'gitea:gitea-owner/platform',
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
await expect(result).rejects.toThrow('Integration connection not found');
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('rejects a connection in another workspace', async () => {
|
|
107
|
+
const service = createService();
|
|
108
|
+
|
|
109
|
+
const result = service.resolveRepository({
|
|
110
|
+
workspaceId: crypto.randomUUID(),
|
|
111
|
+
connectionId: connection.id,
|
|
112
|
+
externalRepositoryId: 'gitea:gitea-owner/platform',
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
await expect(result).rejects.toThrow('requested workspace');
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('surfaces provider repository failures', async () => {
|
|
119
|
+
const service = createService({
|
|
120
|
+
resolveRepository: async () => {
|
|
121
|
+
await Promise.resolve();
|
|
122
|
+
throw new IntegrationProviderError('repository-not-found', 'Repository not found');
|
|
123
|
+
},
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
const result = service.resolveRepository({
|
|
127
|
+
workspaceId,
|
|
128
|
+
connectionId: connection.id,
|
|
129
|
+
externalRepositoryId: 'gitea:gitea-owner/missing',
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
await expect(result).rejects.toMatchObject({reason: 'repository-not-found'});
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('lists files through an active source-control connection', async () => {
|
|
136
|
+
const service = createService();
|
|
137
|
+
|
|
138
|
+
const result = await service.listFiles({
|
|
139
|
+
workspaceId,
|
|
140
|
+
connectionId: connection.id,
|
|
141
|
+
externalRepositoryId: 'gitea:gitea-owner/platform',
|
|
142
|
+
ref: 'main',
|
|
143
|
+
prefix: '.shipfox/workflows/',
|
|
144
|
+
limit: 100,
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
expect(result.files[0]?.path).toBe('.shipfox/workflows/ci.yml');
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it('fetches files through an active source-control connection', async () => {
|
|
151
|
+
const service = createService();
|
|
152
|
+
|
|
153
|
+
const result = await service.fetchFile({
|
|
154
|
+
workspaceId,
|
|
155
|
+
connectionId: connection.id,
|
|
156
|
+
externalRepositoryId: 'gitea:gitea-owner/platform',
|
|
157
|
+
ref: 'main',
|
|
158
|
+
path: '.shipfox/workflows/ci.yml',
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
expect(result.content).toBe('name: CI');
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it('creates a checkout spec through an active source-control connection', async () => {
|
|
165
|
+
const createCheckoutSpec = vi.fn(
|
|
166
|
+
async (input: Parameters<NonNullable<SourceControlProvider['createCheckoutSpec']>>[0]) => {
|
|
167
|
+
await Promise.resolve();
|
|
168
|
+
return {repositoryUrl: repository.cloneUrl, ref: input.ref ?? repository.defaultBranch};
|
|
169
|
+
},
|
|
170
|
+
);
|
|
171
|
+
const service = createService({createCheckoutSpec});
|
|
172
|
+
|
|
173
|
+
const result = await service.createCheckoutSpec({
|
|
174
|
+
workspaceId,
|
|
175
|
+
connectionId: connection.id,
|
|
176
|
+
externalRepositoryId: 'gitea:gitea-owner/platform',
|
|
177
|
+
ref: 'feature/x',
|
|
178
|
+
permissions: {contents: 'write'},
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
expect(result).toEqual({
|
|
182
|
+
repositoryUrl: 'https://gitea.local/gitea-owner/platform.git',
|
|
183
|
+
ref: 'feature/x',
|
|
184
|
+
});
|
|
185
|
+
expect(createCheckoutSpec).toHaveBeenCalledWith(
|
|
186
|
+
expect.objectContaining({permissions: {contents: 'write'}}),
|
|
187
|
+
);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it('rejects a checkout spec for a connection in another workspace', async () => {
|
|
191
|
+
const service = createService();
|
|
192
|
+
|
|
193
|
+
const result = service.createCheckoutSpec({
|
|
194
|
+
workspaceId: crypto.randomUUID(),
|
|
195
|
+
connectionId: connection.id,
|
|
196
|
+
externalRepositoryId: 'gitea:gitea-owner/platform',
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
await expect(result).rejects.toThrow('requested workspace');
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it('surfaces provider checkout failures', async () => {
|
|
203
|
+
const service = createService({
|
|
204
|
+
createCheckoutSpec: async () => {
|
|
205
|
+
await Promise.resolve();
|
|
206
|
+
throw new IntegrationProviderError('access-denied', 'Checkout not permitted');
|
|
207
|
+
},
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
const result = service.createCheckoutSpec({
|
|
211
|
+
workspaceId,
|
|
212
|
+
connectionId: connection.id,
|
|
213
|
+
externalRepositoryId: 'gitea:gitea-owner/platform',
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
await expect(result).rejects.toMatchObject({reason: 'access-denied'});
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it('rejects when the provider does not support creating a checkout spec', async () => {
|
|
220
|
+
const service = createService({}, {omitCheckoutSpec: true});
|
|
221
|
+
|
|
222
|
+
const result = service.createCheckoutSpec({
|
|
223
|
+
workspaceId,
|
|
224
|
+
connectionId: connection.id,
|
|
225
|
+
externalRepositoryId: 'gitea:gitea-owner/platform',
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
await expect(result).rejects.toBeInstanceOf(IntegrationCheckoutUnsupportedError);
|
|
229
|
+
});
|
|
230
|
+
});
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import type {IntegrationConnection} from './entities/connection.js';
|
|
2
|
+
import {
|
|
3
|
+
IntegrationCheckoutUnsupportedError,
|
|
4
|
+
IntegrationConnectionInactiveError,
|
|
5
|
+
IntegrationConnectionNotFoundError,
|
|
6
|
+
IntegrationConnectionWorkspaceMismatchError,
|
|
7
|
+
} from './errors.js';
|
|
8
|
+
import type {IntegrationProviderRegistry} from './providers/registry.js';
|
|
9
|
+
import type {
|
|
10
|
+
CheckoutPermissions,
|
|
11
|
+
CheckoutSpec,
|
|
12
|
+
FilePage,
|
|
13
|
+
FileSnapshot,
|
|
14
|
+
RepositoryPage,
|
|
15
|
+
RepositorySnapshot,
|
|
16
|
+
} from './providers/source-control.js';
|
|
17
|
+
|
|
18
|
+
export interface IntegrationSourceControlService {
|
|
19
|
+
getConnection(connectionId: string): Promise<IntegrationConnection>;
|
|
20
|
+
listRepositories(input: ListSourceRepositoriesInput): Promise<RepositoryPage>;
|
|
21
|
+
resolveRepository(input: ResolveSourceRepositoryInput): Promise<ResolvedSourceRepository>;
|
|
22
|
+
listFiles(input: ListSourceFilesInput): Promise<FilePage>;
|
|
23
|
+
fetchFile(input: FetchSourceFileInput): Promise<FileSnapshot>;
|
|
24
|
+
createCheckoutSpec(input: CreateSourceCheckoutSpecInput): Promise<CheckoutSpec>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface ListSourceRepositoriesInput {
|
|
28
|
+
connection: IntegrationConnection;
|
|
29
|
+
limit: number;
|
|
30
|
+
cursor?: string | undefined;
|
|
31
|
+
search?: string | undefined;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface ResolveSourceRepositoryInput {
|
|
35
|
+
workspaceId: string;
|
|
36
|
+
connectionId: string;
|
|
37
|
+
externalRepositoryId: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface ListSourceFilesInput extends ResolveSourceRepositoryInput {
|
|
41
|
+
ref: string;
|
|
42
|
+
prefix: string;
|
|
43
|
+
limit: number;
|
|
44
|
+
cursor?: string | undefined;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface FetchSourceFileInput extends ResolveSourceRepositoryInput {
|
|
48
|
+
ref: string;
|
|
49
|
+
path: string;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface CreateSourceCheckoutSpecInput extends ResolveSourceRepositoryInput {
|
|
53
|
+
ref?: string | undefined;
|
|
54
|
+
permissions?: CheckoutPermissions | undefined;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface ResolvedSourceRepository {
|
|
58
|
+
connection: IntegrationConnection;
|
|
59
|
+
repository: RepositorySnapshot;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface CreateIntegrationSourceControlServiceOptions {
|
|
63
|
+
registry: IntegrationProviderRegistry;
|
|
64
|
+
getIntegrationConnectionById: (
|
|
65
|
+
connectionId: string,
|
|
66
|
+
) => Promise<IntegrationConnection | undefined>;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function createSourceControlIntegrationService({
|
|
70
|
+
registry,
|
|
71
|
+
getIntegrationConnectionById,
|
|
72
|
+
}: CreateIntegrationSourceControlServiceOptions): IntegrationSourceControlService {
|
|
73
|
+
async function getConnection(connectionId: string): Promise<IntegrationConnection> {
|
|
74
|
+
const connection = await getIntegrationConnectionById(connectionId);
|
|
75
|
+
if (!connection) throw new IntegrationConnectionNotFoundError(connectionId);
|
|
76
|
+
if (connection.lifecycleStatus !== 'active') {
|
|
77
|
+
throw new IntegrationConnectionInactiveError(connection.id);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return connection;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
getConnection,
|
|
85
|
+
|
|
86
|
+
async listRepositories({connection, limit, cursor, search}) {
|
|
87
|
+
const sourceControl = registry.getAdapter(connection.provider, 'source_control');
|
|
88
|
+
return await sourceControl.listRepositories({
|
|
89
|
+
connection,
|
|
90
|
+
limit,
|
|
91
|
+
cursor,
|
|
92
|
+
search,
|
|
93
|
+
});
|
|
94
|
+
},
|
|
95
|
+
|
|
96
|
+
async resolveRepository({workspaceId, connectionId, externalRepositoryId}) {
|
|
97
|
+
const connection = await getConnection(connectionId);
|
|
98
|
+
if (connection.workspaceId !== workspaceId) {
|
|
99
|
+
throw new IntegrationConnectionWorkspaceMismatchError(connectionId);
|
|
100
|
+
}
|
|
101
|
+
const sourceControl = registry.getAdapter(connection.provider, 'source_control');
|
|
102
|
+
|
|
103
|
+
const repository = await sourceControl.resolveRepository({
|
|
104
|
+
connection,
|
|
105
|
+
externalRepositoryId,
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
return {connection, repository};
|
|
109
|
+
},
|
|
110
|
+
|
|
111
|
+
async listFiles({workspaceId, connectionId, externalRepositoryId, ref, prefix, limit, cursor}) {
|
|
112
|
+
const connection = await getConnection(connectionId);
|
|
113
|
+
if (connection.workspaceId !== workspaceId) {
|
|
114
|
+
throw new IntegrationConnectionWorkspaceMismatchError(connectionId);
|
|
115
|
+
}
|
|
116
|
+
const sourceControl = registry.getAdapter(connection.provider, 'source_control');
|
|
117
|
+
|
|
118
|
+
return await sourceControl.listFiles({
|
|
119
|
+
connection,
|
|
120
|
+
externalRepositoryId,
|
|
121
|
+
ref,
|
|
122
|
+
prefix,
|
|
123
|
+
limit,
|
|
124
|
+
cursor,
|
|
125
|
+
});
|
|
126
|
+
},
|
|
127
|
+
|
|
128
|
+
async fetchFile({workspaceId, connectionId, externalRepositoryId, ref, path}) {
|
|
129
|
+
const connection = await getConnection(connectionId);
|
|
130
|
+
if (connection.workspaceId !== workspaceId) {
|
|
131
|
+
throw new IntegrationConnectionWorkspaceMismatchError(connectionId);
|
|
132
|
+
}
|
|
133
|
+
const sourceControl = registry.getAdapter(connection.provider, 'source_control');
|
|
134
|
+
|
|
135
|
+
return await sourceControl.fetchFile({
|
|
136
|
+
connection,
|
|
137
|
+
externalRepositoryId,
|
|
138
|
+
ref,
|
|
139
|
+
path,
|
|
140
|
+
});
|
|
141
|
+
},
|
|
142
|
+
|
|
143
|
+
async createCheckoutSpec({workspaceId, connectionId, externalRepositoryId, ref, permissions}) {
|
|
144
|
+
const connection = await getConnection(connectionId);
|
|
145
|
+
if (connection.workspaceId !== workspaceId) {
|
|
146
|
+
throw new IntegrationConnectionWorkspaceMismatchError(connectionId);
|
|
147
|
+
}
|
|
148
|
+
const sourceControl = registry.getAdapter(connection.provider, 'source_control');
|
|
149
|
+
if (!sourceControl.createCheckoutSpec) {
|
|
150
|
+
throw new IntegrationCheckoutUnsupportedError(connection.provider);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return await sourceControl.createCheckoutSpec({
|
|
154
|
+
connection,
|
|
155
|
+
externalRepositoryId,
|
|
156
|
+
ref,
|
|
157
|
+
permissions,
|
|
158
|
+
});
|
|
159
|
+
},
|
|
160
|
+
};
|
|
161
|
+
}
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
import {ConnectionSlugConflictError} from '@shipfox/api-integration-core-dto';
|
|
2
|
+
import {upsertGithubInstallation} from '@shipfox/api-integration-github';
|
|
3
|
+
import {IntegrationConnectionAlreadyExistsError} from '#core/errors.js';
|
|
4
|
+
import {
|
|
5
|
+
createIntegrationConnection,
|
|
6
|
+
deleteIntegrationConnection,
|
|
7
|
+
getIntegrationConnectionById,
|
|
8
|
+
listIntegrationConnections,
|
|
9
|
+
listIntegrationConnectionsByProvider,
|
|
10
|
+
resolveUniqueConnectionSlug,
|
|
11
|
+
updateIntegrationConnectionLifecycleStatus,
|
|
12
|
+
upsertIntegrationConnection,
|
|
13
|
+
} from './connections.js';
|
|
14
|
+
import {db} from './db.js';
|
|
15
|
+
|
|
16
|
+
describe('integration connection queries', () => {
|
|
17
|
+
let workspaceId: string;
|
|
18
|
+
|
|
19
|
+
beforeEach(() => {
|
|
20
|
+
workspaceId = crypto.randomUUID();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('upserts duplicate external connections for a workspace', async () => {
|
|
24
|
+
const first = await upsertIntegrationConnection({
|
|
25
|
+
workspaceId,
|
|
26
|
+
provider: 'gitea',
|
|
27
|
+
externalAccountId: 'gitea-owner',
|
|
28
|
+
slug: 'gitea_owner',
|
|
29
|
+
displayName: 'Gitea',
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const second = await upsertIntegrationConnection({
|
|
33
|
+
workspaceId,
|
|
34
|
+
provider: 'gitea',
|
|
35
|
+
externalAccountId: 'gitea-owner',
|
|
36
|
+
slug: 'debug_renamed',
|
|
37
|
+
displayName: 'Renamed Debug',
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
expect(second.id).toBe(first.id);
|
|
41
|
+
expect(second.displayName).toBe('Renamed Debug');
|
|
42
|
+
expect(second.slug).toBe('gitea_owner');
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('allows multiple same-provider connections when external account differs', async () => {
|
|
46
|
+
await upsertIntegrationConnection({
|
|
47
|
+
workspaceId,
|
|
48
|
+
provider: 'gitea',
|
|
49
|
+
externalAccountId: 'debug-1',
|
|
50
|
+
slug: 'debug_1',
|
|
51
|
+
displayName: 'Debug One',
|
|
52
|
+
});
|
|
53
|
+
await upsertIntegrationConnection({
|
|
54
|
+
workspaceId,
|
|
55
|
+
provider: 'gitea',
|
|
56
|
+
externalAccountId: 'debug-2',
|
|
57
|
+
slug: 'debug_2',
|
|
58
|
+
displayName: 'Debug Two',
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
const result = await listIntegrationConnections({workspaceId});
|
|
62
|
+
|
|
63
|
+
expect(result).toHaveLength(2);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('resolves a unique slug in workspace scope', async () => {
|
|
67
|
+
await upsertIntegrationConnection({
|
|
68
|
+
workspaceId,
|
|
69
|
+
provider: 'gitea',
|
|
70
|
+
externalAccountId: 'debug-1',
|
|
71
|
+
slug: 'gitea_owner',
|
|
72
|
+
displayName: 'Debug One',
|
|
73
|
+
});
|
|
74
|
+
await upsertIntegrationConnection({
|
|
75
|
+
workspaceId,
|
|
76
|
+
provider: 'github',
|
|
77
|
+
externalAccountId: 'debug-2',
|
|
78
|
+
slug: 'debug_2',
|
|
79
|
+
displayName: 'Debug Two',
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
const result = await resolveUniqueConnectionSlug({
|
|
83
|
+
workspaceId,
|
|
84
|
+
provider: 'gitea',
|
|
85
|
+
externalAccountId: 'debug-3',
|
|
86
|
+
baseSlug: 'debug',
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
expect(result).toBe('debug');
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('keeps the existing slug when resolving a reconnect', async () => {
|
|
93
|
+
await upsertIntegrationConnection({
|
|
94
|
+
workspaceId,
|
|
95
|
+
provider: 'sentry',
|
|
96
|
+
externalAccountId: 'install-uuid',
|
|
97
|
+
slug: 'sentry_prod',
|
|
98
|
+
displayName: 'Sentry',
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
const result = await resolveUniqueConnectionSlug({
|
|
102
|
+
workspaceId,
|
|
103
|
+
provider: 'sentry',
|
|
104
|
+
externalAccountId: 'install-uuid',
|
|
105
|
+
baseSlug: 'sentry_renamed',
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
expect(result).toBe('sentry_prod');
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('creates a connection without upserting duplicates', async () => {
|
|
112
|
+
const first = await createIntegrationConnection({
|
|
113
|
+
workspaceId,
|
|
114
|
+
provider: 'webhook',
|
|
115
|
+
externalAccountId: 'stripe',
|
|
116
|
+
slug: 'stripe',
|
|
117
|
+
displayName: 'Stripe',
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
const result = createIntegrationConnection({
|
|
121
|
+
workspaceId,
|
|
122
|
+
provider: 'webhook',
|
|
123
|
+
externalAccountId: 'stripe',
|
|
124
|
+
slug: 'stripe_renamed',
|
|
125
|
+
displayName: 'Renamed Stripe',
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
await expect(result).rejects.toBeInstanceOf(IntegrationConnectionAlreadyExistsError);
|
|
129
|
+
const connections = await listIntegrationConnections({workspaceId});
|
|
130
|
+
expect(connections).toHaveLength(1);
|
|
131
|
+
expect(connections[0]?.id).toBe(first.id);
|
|
132
|
+
expect(connections[0]?.displayName).toBe('Stripe');
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('reports slug collisions separately from duplicate external accounts', async () => {
|
|
136
|
+
await createIntegrationConnection({
|
|
137
|
+
workspaceId,
|
|
138
|
+
provider: 'webhook',
|
|
139
|
+
externalAccountId: 'stripe',
|
|
140
|
+
slug: 'stripe',
|
|
141
|
+
displayName: 'Stripe',
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
const result = createIntegrationConnection({
|
|
145
|
+
workspaceId,
|
|
146
|
+
provider: 'webhook',
|
|
147
|
+
externalAccountId: 'stripe-prod',
|
|
148
|
+
slug: 'stripe',
|
|
149
|
+
displayName: 'Stripe prod',
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
await expect(result).rejects.toBeInstanceOf(ConnectionSlugConflictError);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it('lists workspace connections across all lifecycle statuses', async () => {
|
|
156
|
+
await upsertIntegrationConnection({
|
|
157
|
+
workspaceId,
|
|
158
|
+
provider: 'gitea',
|
|
159
|
+
externalAccountId: 'gitea-owner',
|
|
160
|
+
slug: 'gitea_owner',
|
|
161
|
+
displayName: 'Gitea',
|
|
162
|
+
});
|
|
163
|
+
await upsertIntegrationConnection({
|
|
164
|
+
workspaceId,
|
|
165
|
+
provider: 'github',
|
|
166
|
+
externalAccountId: 'team-1',
|
|
167
|
+
slug: 'github_team_1',
|
|
168
|
+
displayName: 'GitHub',
|
|
169
|
+
});
|
|
170
|
+
await upsertIntegrationConnection({
|
|
171
|
+
workspaceId,
|
|
172
|
+
provider: 'github',
|
|
173
|
+
externalAccountId: 'installation-1',
|
|
174
|
+
slug: 'github_installation_1',
|
|
175
|
+
displayName: 'GitHub',
|
|
176
|
+
lifecycleStatus: 'disabled',
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
const result = await listIntegrationConnections({workspaceId});
|
|
180
|
+
|
|
181
|
+
expect(result.map((connection) => [connection.provider, connection.lifecycleStatus])).toEqual([
|
|
182
|
+
['gitea', 'active'],
|
|
183
|
+
['github', 'active'],
|
|
184
|
+
['github', 'disabled'],
|
|
185
|
+
]);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it('lists connections for a provider across all workspaces', async () => {
|
|
189
|
+
const otherWorkspaceId = crypto.randomUUID();
|
|
190
|
+
const debugA = await upsertIntegrationConnection({
|
|
191
|
+
workspaceId,
|
|
192
|
+
provider: 'gitea',
|
|
193
|
+
externalAccountId: 'gitea-owner',
|
|
194
|
+
slug: 'gitea_owner',
|
|
195
|
+
displayName: 'Gitea',
|
|
196
|
+
});
|
|
197
|
+
const debugB = await upsertIntegrationConnection({
|
|
198
|
+
workspaceId: otherWorkspaceId,
|
|
199
|
+
provider: 'gitea',
|
|
200
|
+
externalAccountId: 'gitea-owner',
|
|
201
|
+
slug: 'gitea_owner',
|
|
202
|
+
displayName: 'Gitea',
|
|
203
|
+
});
|
|
204
|
+
const github = await upsertIntegrationConnection({
|
|
205
|
+
workspaceId,
|
|
206
|
+
provider: 'github',
|
|
207
|
+
externalAccountId: 'gh-1',
|
|
208
|
+
slug: 'github_gh_1',
|
|
209
|
+
displayName: 'GitHub',
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
const result = await listIntegrationConnectionsByProvider({provider: 'gitea'});
|
|
213
|
+
|
|
214
|
+
const ids = result.map((connection) => connection.id);
|
|
215
|
+
expect(result.every((connection) => connection.provider === 'gitea')).toBe(true);
|
|
216
|
+
expect(ids).toEqual(expect.arrayContaining([debugA.id, debugB.id]));
|
|
217
|
+
expect(ids).not.toContain(github.id);
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it('updates a connection lifecycle status and returns the mapped connection', async () => {
|
|
221
|
+
const connection = await upsertIntegrationConnection({
|
|
222
|
+
workspaceId,
|
|
223
|
+
provider: 'sentry',
|
|
224
|
+
externalAccountId: 'install-uuid',
|
|
225
|
+
slug: 'sentry_install_uuid',
|
|
226
|
+
displayName: 'Sentry acme',
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
const updated = await updateIntegrationConnectionLifecycleStatus({
|
|
230
|
+
id: connection.id,
|
|
231
|
+
lifecycleStatus: 'disabled',
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
expect(updated?.lifecycleStatus).toBe('disabled');
|
|
235
|
+
const reloaded = await getIntegrationConnectionById(connection.id);
|
|
236
|
+
expect(reloaded?.lifecycleStatus).toBe('disabled');
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
it('returns undefined when updating the lifecycle status of an unknown connection', async () => {
|
|
240
|
+
const result = await updateIntegrationConnectionLifecycleStatus({
|
|
241
|
+
id: crypto.randomUUID(),
|
|
242
|
+
lifecycleStatus: 'disabled',
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
expect(result).toBeUndefined();
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
it('deletes a connection and reports whether a row was removed', async () => {
|
|
249
|
+
const connection = await upsertIntegrationConnection({
|
|
250
|
+
workspaceId,
|
|
251
|
+
provider: 'webhook',
|
|
252
|
+
externalAccountId: 'stripe',
|
|
253
|
+
slug: 'stripe',
|
|
254
|
+
displayName: 'Stripe',
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
const deleted = await deleteIntegrationConnection({id: connection.id});
|
|
258
|
+
const deletedAgain = await deleteIntegrationConnection({id: connection.id});
|
|
259
|
+
|
|
260
|
+
expect(deleted).toBe(true);
|
|
261
|
+
expect(deletedAgain).toBe(false);
|
|
262
|
+
expect(await getIntegrationConnectionById(connection.id)).toBeUndefined();
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
it('rolls back a connection when provider-specific installation persistence fails', async () => {
|
|
266
|
+
const result = db().transaction(async (tx) => {
|
|
267
|
+
const connection = await upsertIntegrationConnection(
|
|
268
|
+
{
|
|
269
|
+
workspaceId,
|
|
270
|
+
provider: 'github',
|
|
271
|
+
externalAccountId: '123',
|
|
272
|
+
slug: 'github_123',
|
|
273
|
+
displayName: 'GitHub shipfox',
|
|
274
|
+
},
|
|
275
|
+
{tx},
|
|
276
|
+
);
|
|
277
|
+
|
|
278
|
+
await upsertGithubInstallation(
|
|
279
|
+
{
|
|
280
|
+
connectionId: connection.id,
|
|
281
|
+
installationId: '123',
|
|
282
|
+
accountLogin: null as unknown as string,
|
|
283
|
+
accountType: 'Organization',
|
|
284
|
+
repositorySelection: 'all',
|
|
285
|
+
latestEvent: {id: 123},
|
|
286
|
+
},
|
|
287
|
+
{tx},
|
|
288
|
+
);
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
await expect(result).rejects.toThrow();
|
|
292
|
+
|
|
293
|
+
const connections = await listIntegrationConnections({workspaceId});
|
|
294
|
+
expect(connections).toHaveLength(0);
|
|
295
|
+
});
|
|
296
|
+
});
|