@superblocksteam/shared 0.9607.3 → 0.9608.1
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/dist/database-lifecycle/appDatabaseCompatibility.d.ts +49 -0
- package/dist/database-lifecycle/appDatabaseCompatibility.d.ts.map +1 -0
- package/dist/database-lifecycle/appDatabaseCompatibility.js +32 -0
- package/dist/database-lifecycle/appDatabaseCompatibility.js.map +1 -0
- package/dist/database-lifecycle/appDatabaseCompatibility.test.d.ts +2 -0
- package/dist/database-lifecycle/appDatabaseCompatibility.test.d.ts.map +1 -0
- package/dist/database-lifecycle/appDatabaseCompatibility.test.js +47 -0
- package/dist/database-lifecycle/appDatabaseCompatibility.test.js.map +1 -0
- package/dist/database-lifecycle/destructiveMigrations.d.ts +12 -0
- package/dist/database-lifecycle/destructiveMigrations.d.ts.map +1 -1
- package/dist/database-lifecycle/destructiveMigrations.js +114 -0
- package/dist/database-lifecycle/destructiveMigrations.js.map +1 -1
- package/dist/database-lifecycle/destructiveMigrations.test.js +79 -0
- package/dist/database-lifecycle/destructiveMigrations.test.js.map +1 -1
- package/dist/database-lifecycle/index.d.ts +46 -2
- package/dist/database-lifecycle/index.d.ts.map +1 -1
- package/dist/database-lifecycle/index.js +127 -1
- package/dist/database-lifecycle/index.js.map +1 -1
- package/dist/database-lifecycle/providerDiagnostics.test.d.ts +2 -0
- package/dist/database-lifecycle/providerDiagnostics.test.d.ts.map +1 -0
- package/dist/database-lifecycle/providerDiagnostics.test.js +119 -0
- package/dist/database-lifecycle/providerDiagnostics.test.js.map +1 -0
- package/dist/environment-compatibility/appEnvironmentCompatibility.d.ts +65 -0
- package/dist/environment-compatibility/appEnvironmentCompatibility.d.ts.map +1 -0
- package/dist/environment-compatibility/appEnvironmentCompatibility.js +87 -0
- package/dist/environment-compatibility/appEnvironmentCompatibility.js.map +1 -0
- package/dist/environment-compatibility/appEnvironmentCompatibility.test.d.ts +2 -0
- package/dist/environment-compatibility/appEnvironmentCompatibility.test.d.ts.map +1 -0
- package/dist/environment-compatibility/appEnvironmentCompatibility.test.js +285 -0
- package/dist/environment-compatibility/appEnvironmentCompatibility.test.js.map +1 -0
- package/dist/errors/http.d.ts +15 -4
- package/dist/errors/http.d.ts.map +1 -1
- package/dist/errors/http.js +25 -7
- package/dist/errors/http.js.map +1 -1
- package/dist/errors/http.test.d.ts +2 -0
- package/dist/errors/http.test.d.ts.map +1 -0
- package/dist/errors/http.test.js +28 -0
- package/dist/errors/http.test.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/plugins/templates/restApiIntegration.d.ts.map +1 -1
- package/dist/plugins/templates/restApiIntegration.js +2 -0
- package/dist/plugins/templates/restApiIntegration.js.map +1 -1
- package/dist/plugins/templates/shared/auth.d.ts +3 -1
- package/dist/plugins/templates/shared/auth.d.ts.map +1 -1
- package/dist/plugins/templates/shared/auth.js +129 -17
- package/dist/plugins/templates/shared/auth.js.map +1 -1
- package/dist/plugins/templates/shared/auth.test.d.ts +2 -0
- package/dist/plugins/templates/shared/auth.test.d.ts.map +1 -0
- package/dist/plugins/templates/shared/auth.test.js +116 -0
- package/dist/plugins/templates/shared/auth.test.js.map +1 -0
- package/dist/plugins/templates/shared/restapiintegration.d.ts +2 -0
- package/dist/plugins/templates/shared/restapiintegration.d.ts.map +1 -1
- package/dist/plugins/templates/shared/restapiintegration.js +3 -1
- package/dist/plugins/templates/shared/restapiintegration.js.map +1 -1
- package/dist/socket/dist-entry.test.d.ts +2 -0
- package/dist/socket/dist-entry.test.d.ts.map +1 -0
- package/dist/socket/dist-entry.test.js +34 -0
- package/dist/socket/dist-entry.test.js.map +1 -0
- package/dist/socket/heartbeat.d.ts +258 -0
- package/dist/socket/heartbeat.d.ts.map +1 -0
- package/dist/socket/heartbeat.js +307 -0
- package/dist/socket/heartbeat.js.map +1 -0
- package/dist/socket/heartbeat.test.d.ts +2 -0
- package/dist/socket/heartbeat.test.d.ts.map +1 -0
- package/dist/socket/heartbeat.test.js +431 -0
- package/dist/socket/heartbeat.test.js.map +1 -0
- package/dist/socket/index.d.ts +1 -0
- package/dist/socket/index.d.ts.map +1 -1
- package/dist/socket/index.js +1 -0
- package/dist/socket/index.js.map +1 -1
- package/dist/socket/util.d.ts.map +1 -1
- package/dist/socket/util.js +15 -1
- package/dist/socket/util.js.map +1 -1
- package/dist/socket/util.test.js +12 -2
- package/dist/socket/util.test.js.map +1 -1
- package/dist/types/ai/index.d.ts +3 -0
- package/dist/types/ai/index.d.ts.map +1 -1
- package/dist/types/ai/index.js +3 -0
- package/dist/types/ai/index.js.map +1 -1
- package/dist/types/application/index.d.ts +12 -0
- package/dist/types/application/index.d.ts.map +1 -1
- package/dist/types/application/index.js.map +1 -1
- package/dist/types/codeSecurity/index.d.ts +55 -1
- package/dist/types/codeSecurity/index.d.ts.map +1 -1
- package/dist/types/codeSecurity/index.js +21 -1
- package/dist/types/codeSecurity/index.js.map +1 -1
- package/dist/types/codeSecurity/index.test.d.ts +2 -0
- package/dist/types/codeSecurity/index.test.d.ts.map +1 -0
- package/dist/types/codeSecurity/index.test.js +19 -0
- package/dist/types/codeSecurity/index.test.js.map +1 -0
- package/dist/types/datasource/auth.d.ts +12 -1
- package/dist/types/datasource/auth.d.ts.map +1 -1
- package/dist/types/datasource/auth.js +20 -2
- package/dist/types/datasource/auth.js.map +1 -1
- package/dist/types/datasource/auth.test.js +3 -1
- package/dist/types/datasource/auth.test.js.map +1 -1
- package/dist/types/group/index.d.ts +1 -3
- package/dist/types/group/index.d.ts.map +1 -1
- package/dist/types/group/index.js +1 -3
- package/dist/types/group/index.js.map +1 -1
- package/dist/types/integration/index.d.ts +11 -1
- package/dist/types/integration/index.d.ts.map +1 -1
- package/dist/types/integration/index.js +11 -1
- package/dist/types/integration/index.js.map +1 -1
- package/dist/types/plugin/form.d.ts +32 -0
- package/dist/types/plugin/form.d.ts.map +1 -1
- package/dist/types/plugin/form.js +73 -1
- package/dist/types/plugin/form.js.map +1 -1
- package/dist/types/plugin/form.test.js +128 -0
- package/dist/types/plugin/form.test.js.map +1 -1
- package/dist/types/profile/index.d.ts +16 -1
- package/dist/types/profile/index.d.ts.map +1 -1
- package/dist/types/profile/index.js +7 -1
- package/dist/types/profile/index.js.map +1 -1
- package/dist/types/rbac/index.d.ts +2 -0
- package/dist/types/rbac/index.d.ts.map +1 -1
- package/dist/types/rbac/index.js +2 -0
- package/dist/types/rbac/index.js.map +1 -1
- package/dist/types/response/GetRecentApplicationsResponseBody.d.ts +4 -0
- package/dist/types/response/GetRecentApplicationsResponseBody.d.ts.map +1 -0
- package/dist/types/response/GetRecentApplicationsResponseBody.js +3 -0
- package/dist/types/response/GetRecentApplicationsResponseBody.js.map +1 -0
- package/dist/types/response/index.d.ts +1 -0
- package/dist/types/response/index.d.ts.map +1 -1
- package/dist/types/response/index.js +1 -0
- package/dist/types/response/index.js.map +1 -1
- package/dist-esm/database-lifecycle/appDatabaseCompatibility.d.ts +49 -0
- package/dist-esm/database-lifecycle/appDatabaseCompatibility.d.ts.map +1 -0
- package/dist-esm/database-lifecycle/appDatabaseCompatibility.js +28 -0
- package/dist-esm/database-lifecycle/appDatabaseCompatibility.js.map +1 -0
- package/dist-esm/database-lifecycle/appDatabaseCompatibility.test.d.ts +2 -0
- package/dist-esm/database-lifecycle/appDatabaseCompatibility.test.d.ts.map +1 -0
- package/dist-esm/database-lifecycle/appDatabaseCompatibility.test.js +45 -0
- package/dist-esm/database-lifecycle/appDatabaseCompatibility.test.js.map +1 -0
- package/dist-esm/database-lifecycle/destructiveMigrations.d.ts +12 -0
- package/dist-esm/database-lifecycle/destructiveMigrations.d.ts.map +1 -1
- package/dist-esm/database-lifecycle/destructiveMigrations.js +113 -0
- package/dist-esm/database-lifecycle/destructiveMigrations.js.map +1 -1
- package/dist-esm/database-lifecycle/destructiveMigrations.test.js +80 -1
- package/dist-esm/database-lifecycle/destructiveMigrations.test.js.map +1 -1
- package/dist-esm/database-lifecycle/index.d.ts +46 -2
- package/dist-esm/database-lifecycle/index.d.ts.map +1 -1
- package/dist-esm/database-lifecycle/index.js +123 -1
- package/dist-esm/database-lifecycle/index.js.map +1 -1
- package/dist-esm/database-lifecycle/providerDiagnostics.test.d.ts +2 -0
- package/dist-esm/database-lifecycle/providerDiagnostics.test.d.ts.map +1 -0
- package/dist-esm/database-lifecycle/providerDiagnostics.test.js +117 -0
- package/dist-esm/database-lifecycle/providerDiagnostics.test.js.map +1 -0
- package/dist-esm/environment-compatibility/appEnvironmentCompatibility.d.ts +65 -0
- package/dist-esm/environment-compatibility/appEnvironmentCompatibility.d.ts.map +1 -0
- package/dist-esm/environment-compatibility/appEnvironmentCompatibility.js +79 -0
- package/dist-esm/environment-compatibility/appEnvironmentCompatibility.js.map +1 -0
- package/dist-esm/environment-compatibility/appEnvironmentCompatibility.test.d.ts +2 -0
- package/dist-esm/environment-compatibility/appEnvironmentCompatibility.test.d.ts.map +1 -0
- package/dist-esm/environment-compatibility/appEnvironmentCompatibility.test.js +283 -0
- package/dist-esm/environment-compatibility/appEnvironmentCompatibility.test.js.map +1 -0
- package/dist-esm/errors/http.d.ts +15 -4
- package/dist-esm/errors/http.d.ts.map +1 -1
- package/dist-esm/errors/http.js +22 -6
- package/dist-esm/errors/http.js.map +1 -1
- package/dist-esm/errors/http.test.d.ts +2 -0
- package/dist-esm/errors/http.test.d.ts.map +1 -0
- package/dist-esm/errors/http.test.js +26 -0
- package/dist-esm/errors/http.test.js.map +1 -0
- package/dist-esm/index.d.ts +2 -0
- package/dist-esm/index.d.ts.map +1 -1
- package/dist-esm/index.js +2 -0
- package/dist-esm/index.js.map +1 -1
- package/dist-esm/plugins/templates/restApiIntegration.d.ts.map +1 -1
- package/dist-esm/plugins/templates/restApiIntegration.js +2 -0
- package/dist-esm/plugins/templates/restApiIntegration.js.map +1 -1
- package/dist-esm/plugins/templates/shared/auth.d.ts +3 -1
- package/dist-esm/plugins/templates/shared/auth.d.ts.map +1 -1
- package/dist-esm/plugins/templates/shared/auth.js +129 -17
- package/dist-esm/plugins/templates/shared/auth.js.map +1 -1
- package/dist-esm/plugins/templates/shared/auth.test.d.ts +2 -0
- package/dist-esm/plugins/templates/shared/auth.test.d.ts.map +1 -0
- package/dist-esm/plugins/templates/shared/auth.test.js +114 -0
- package/dist-esm/plugins/templates/shared/auth.test.js.map +1 -0
- package/dist-esm/plugins/templates/shared/restapiintegration.d.ts +2 -0
- package/dist-esm/plugins/templates/shared/restapiintegration.d.ts.map +1 -1
- package/dist-esm/plugins/templates/shared/restapiintegration.js +3 -1
- package/dist-esm/plugins/templates/shared/restapiintegration.js.map +1 -1
- package/dist-esm/socket/dist-entry.test.d.ts +2 -0
- package/dist-esm/socket/dist-entry.test.d.ts.map +1 -0
- package/dist-esm/socket/dist-entry.test.js +29 -0
- package/dist-esm/socket/dist-entry.test.js.map +1 -0
- package/dist-esm/socket/heartbeat.d.ts +258 -0
- package/dist-esm/socket/heartbeat.d.ts.map +1 -0
- package/dist-esm/socket/heartbeat.js +303 -0
- package/dist-esm/socket/heartbeat.js.map +1 -0
- package/dist-esm/socket/heartbeat.test.d.ts +2 -0
- package/dist-esm/socket/heartbeat.test.d.ts.map +1 -0
- package/dist-esm/socket/heartbeat.test.js +429 -0
- package/dist-esm/socket/heartbeat.test.js.map +1 -0
- package/dist-esm/socket/index.d.ts +1 -0
- package/dist-esm/socket/index.d.ts.map +1 -1
- package/dist-esm/socket/index.js +1 -0
- package/dist-esm/socket/index.js.map +1 -1
- package/dist-esm/socket/util.d.ts.map +1 -1
- package/dist-esm/socket/util.js +15 -1
- package/dist-esm/socket/util.js.map +1 -1
- package/dist-esm/socket/util.test.js +12 -2
- package/dist-esm/socket/util.test.js.map +1 -1
- package/dist-esm/types/ai/index.d.ts +3 -0
- package/dist-esm/types/ai/index.d.ts.map +1 -1
- package/dist-esm/types/ai/index.js +3 -0
- package/dist-esm/types/ai/index.js.map +1 -1
- package/dist-esm/types/application/index.d.ts +12 -0
- package/dist-esm/types/application/index.d.ts.map +1 -1
- package/dist-esm/types/application/index.js.map +1 -1
- package/dist-esm/types/codeSecurity/index.d.ts +55 -1
- package/dist-esm/types/codeSecurity/index.d.ts.map +1 -1
- package/dist-esm/types/codeSecurity/index.js +17 -1
- package/dist-esm/types/codeSecurity/index.js.map +1 -1
- package/dist-esm/types/codeSecurity/index.test.d.ts +2 -0
- package/dist-esm/types/codeSecurity/index.test.d.ts.map +1 -0
- package/dist-esm/types/codeSecurity/index.test.js +17 -0
- package/dist-esm/types/codeSecurity/index.test.js.map +1 -0
- package/dist-esm/types/datasource/auth.d.ts +12 -1
- package/dist-esm/types/datasource/auth.d.ts.map +1 -1
- package/dist-esm/types/datasource/auth.js +19 -2
- package/dist-esm/types/datasource/auth.js.map +1 -1
- package/dist-esm/types/datasource/auth.test.js +3 -1
- package/dist-esm/types/datasource/auth.test.js.map +1 -1
- package/dist-esm/types/group/index.d.ts +1 -3
- package/dist-esm/types/group/index.d.ts.map +1 -1
- package/dist-esm/types/group/index.js +1 -3
- package/dist-esm/types/group/index.js.map +1 -1
- package/dist-esm/types/integration/index.d.ts +11 -1
- package/dist-esm/types/integration/index.d.ts.map +1 -1
- package/dist-esm/types/integration/index.js +11 -1
- package/dist-esm/types/integration/index.js.map +1 -1
- package/dist-esm/types/plugin/form.d.ts +32 -0
- package/dist-esm/types/plugin/form.d.ts.map +1 -1
- package/dist-esm/types/plugin/form.js +68 -0
- package/dist-esm/types/plugin/form.js.map +1 -1
- package/dist-esm/types/plugin/form.test.js +129 -1
- package/dist-esm/types/plugin/form.test.js.map +1 -1
- package/dist-esm/types/profile/index.d.ts +16 -1
- package/dist-esm/types/profile/index.d.ts.map +1 -1
- package/dist-esm/types/profile/index.js +7 -1
- package/dist-esm/types/profile/index.js.map +1 -1
- package/dist-esm/types/rbac/index.d.ts +2 -0
- package/dist-esm/types/rbac/index.d.ts.map +1 -1
- package/dist-esm/types/rbac/index.js +2 -0
- package/dist-esm/types/rbac/index.js.map +1 -1
- package/dist-esm/types/response/GetRecentApplicationsResponseBody.d.ts +4 -0
- package/dist-esm/types/response/GetRecentApplicationsResponseBody.d.ts.map +1 -0
- package/dist-esm/types/response/GetRecentApplicationsResponseBody.js +2 -0
- package/dist-esm/types/response/GetRecentApplicationsResponseBody.js.map +1 -0
- package/dist-esm/types/response/index.d.ts +1 -0
- package/dist-esm/types/response/index.d.ts.map +1 -1
- package/dist-esm/types/response/index.js +1 -0
- package/dist-esm/types/response/index.js.map +1 -1
- package/package.json +1 -1
- package/src/database-lifecycle/appDatabaseCompatibility.test.ts +55 -0
- package/src/database-lifecycle/appDatabaseCompatibility.ts +62 -0
- package/src/database-lifecycle/destructiveMigrations.test.ts +108 -1
- package/src/database-lifecycle/destructiveMigrations.ts +129 -0
- package/src/database-lifecycle/index.ts +173 -0
- package/src/database-lifecycle/providerDiagnostics.test.ts +134 -0
- package/src/environment-compatibility/appEnvironmentCompatibility.test.ts +326 -0
- package/src/environment-compatibility/appEnvironmentCompatibility.ts +173 -0
- package/src/errors/http.test.ts +32 -0
- package/src/errors/http.ts +24 -6
- package/src/index.ts +2 -0
- package/src/plugins/templates/restApiIntegration.ts +2 -0
- package/src/plugins/templates/shared/auth.test.ts +162 -0
- package/src/plugins/templates/shared/auth.ts +135 -18
- package/src/plugins/templates/shared/restapiintegration.ts +3 -1
- package/src/socket/dist-entry.test.ts +29 -0
- package/src/socket/heartbeat.test.ts +538 -0
- package/src/socket/heartbeat.ts +469 -0
- package/src/socket/index.ts +1 -0
- package/src/socket/util.test.ts +14 -2
- package/src/socket/util.ts +15 -1
- package/src/types/ai/index.ts +3 -0
- package/src/types/application/index.ts +14 -0
- package/src/types/codeSecurity/index.test.ts +21 -0
- package/src/types/codeSecurity/index.ts +83 -1
- package/src/types/datasource/auth.test.ts +3 -1
- package/src/types/datasource/auth.ts +24 -3
- package/src/types/group/index.ts +1 -3
- package/src/types/integration/index.ts +14 -0
- package/src/types/plugin/form.test.ts +174 -1
- package/src/types/plugin/form.ts +100 -2
- package/src/types/profile/index.ts +19 -1
- package/src/types/rbac/index.ts +2 -0
- package/src/types/response/GetRecentApplicationsResponseBody.ts +4 -0
- package/src/types/response/index.ts +1 -0
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { describe, expect, test } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { inspectLifecycleProviderDiagnosticCandidate, sanitizeLifecycleProviderDiagnostics } from './index.js';
|
|
4
|
+
|
|
5
|
+
describe('lifecycle provider diagnostic sanitization', () => {
|
|
6
|
+
test('derives canonical AWS fields and reports a stripped request ID', () => {
|
|
7
|
+
expect(
|
|
8
|
+
inspectLifecycleProviderDiagnosticCandidate({
|
|
9
|
+
errorCode: 'AccessDenied',
|
|
10
|
+
nextAction: 'untrusted-action',
|
|
11
|
+
operation: 'CreateDBCluster',
|
|
12
|
+
phase: 'future-phase',
|
|
13
|
+
provider: 'aws',
|
|
14
|
+
requestId: 'not-a-request-id',
|
|
15
|
+
retryable: false,
|
|
16
|
+
service: 'rds',
|
|
17
|
+
summary: 'provider-authored text'
|
|
18
|
+
})
|
|
19
|
+
).toEqual({
|
|
20
|
+
diagnostic: {
|
|
21
|
+
errorCode: 'AccessDenied',
|
|
22
|
+
nextAction: 'review_aws_operation',
|
|
23
|
+
operation: 'CreateDBCluster',
|
|
24
|
+
phase: 'unknown',
|
|
25
|
+
provider: 'aws',
|
|
26
|
+
retryable: false,
|
|
27
|
+
service: 'rds'
|
|
28
|
+
},
|
|
29
|
+
provider: 'aws',
|
|
30
|
+
sanitizedFields: ['requestId']
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test.each([
|
|
35
|
+
['rds', 'CreateDBCluster', 'AccessDenied', '123e4567-e89b-12d3-a456-426614174000'],
|
|
36
|
+
['s3', 'CreateBucket', 'AccessDenied', 'A1B2C3D4E5F6G7H8'],
|
|
37
|
+
['dynamodb', 'CreateTable', 'AccessDeniedException', 'A1B2C3D4E5F6G7H8I9J0K1L2M3N4O5P6Q7R8S9T0U1V2W3X4Y5Z6']
|
|
38
|
+
])('preserves a valid %s request ID', (service, operation, errorCode, requestId) => {
|
|
39
|
+
expect(
|
|
40
|
+
inspectLifecycleProviderDiagnosticCandidate({
|
|
41
|
+
errorCode,
|
|
42
|
+
operation,
|
|
43
|
+
phase: 'apply',
|
|
44
|
+
provider: 'aws',
|
|
45
|
+
requestId,
|
|
46
|
+
retryable: false,
|
|
47
|
+
service
|
|
48
|
+
})
|
|
49
|
+
).toEqual({
|
|
50
|
+
diagnostic: {
|
|
51
|
+
errorCode,
|
|
52
|
+
nextAction: 'review_aws_operation',
|
|
53
|
+
operation,
|
|
54
|
+
phase: 'apply',
|
|
55
|
+
provider: 'aws',
|
|
56
|
+
requestId,
|
|
57
|
+
retryable: false,
|
|
58
|
+
service
|
|
59
|
+
},
|
|
60
|
+
provider: 'aws'
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test('does not apply service-specific request ID formats to unrelated services', () => {
|
|
65
|
+
expect(
|
|
66
|
+
inspectLifecycleProviderDiagnosticCandidate({
|
|
67
|
+
errorCode: 'AccessDenied',
|
|
68
|
+
operation: 'CreateDBCluster',
|
|
69
|
+
phase: 'apply',
|
|
70
|
+
provider: 'aws',
|
|
71
|
+
requestId: 'A1B2C3D4E5F6G7H8',
|
|
72
|
+
retryable: false,
|
|
73
|
+
service: 'rds'
|
|
74
|
+
})
|
|
75
|
+
).toEqual({
|
|
76
|
+
diagnostic: {
|
|
77
|
+
errorCode: 'AccessDenied',
|
|
78
|
+
nextAction: 'review_aws_operation',
|
|
79
|
+
operation: 'CreateDBCluster',
|
|
80
|
+
phase: 'apply',
|
|
81
|
+
provider: 'aws',
|
|
82
|
+
retryable: false,
|
|
83
|
+
service: 'rds'
|
|
84
|
+
},
|
|
85
|
+
provider: 'aws',
|
|
86
|
+
sanitizedFields: ['requestId']
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test('reports rejected provider fields without retaining input text', () => {
|
|
91
|
+
expect(
|
|
92
|
+
inspectLifecycleProviderDiagnosticCandidate({
|
|
93
|
+
errorCode: 'Access Denied',
|
|
94
|
+
operation: 'Create DB Cluster',
|
|
95
|
+
provider: 'aws',
|
|
96
|
+
retryable: 'false',
|
|
97
|
+
service: 'RDS'
|
|
98
|
+
})
|
|
99
|
+
).toEqual({
|
|
100
|
+
provider: 'aws',
|
|
101
|
+
rejectedFields: ['errorCode', 'operation', 'retryable', 'service']
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test('applies the persistence cap after validation', () => {
|
|
106
|
+
const valid = {
|
|
107
|
+
nextAction: 'ignored',
|
|
108
|
+
phase: 'apply',
|
|
109
|
+
provider: 'postgresql',
|
|
110
|
+
retryable: false,
|
|
111
|
+
sqlState: '28P01'
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
expect(
|
|
115
|
+
sanitizeLifecycleProviderDiagnostics([
|
|
116
|
+
{ provider: 'postgresql', sqlState: 'invalid' },
|
|
117
|
+
valid,
|
|
118
|
+
{ ...valid, sqlState: '42P07' },
|
|
119
|
+
{ ...valid, sqlState: '08006' },
|
|
120
|
+
{ ...valid, sqlState: '42501' }
|
|
121
|
+
])
|
|
122
|
+
).toEqual([
|
|
123
|
+
{ nextAction: 'review_database_operation', phase: 'apply', provider: 'postgresql', retryable: false, sqlState: '28P01' },
|
|
124
|
+
{ nextAction: 'review_database_operation', phase: 'apply', provider: 'postgresql', retryable: false, sqlState: '42P07' },
|
|
125
|
+
{ nextAction: 'review_database_operation', phase: 'apply', provider: 'postgresql', retryable: false, sqlState: '08006' }
|
|
126
|
+
]);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
test('treats non-array payloads and arrays without valid entries as absent', () => {
|
|
130
|
+
expect(sanitizeLifecycleProviderDiagnostics(null)).toBeUndefined();
|
|
131
|
+
expect(sanitizeLifecycleProviderDiagnostics({ provider: 'aws' })).toBeUndefined();
|
|
132
|
+
expect(sanitizeLifecycleProviderDiagnostics([{ provider: 'unknown' }])).toBeUndefined();
|
|
133
|
+
});
|
|
134
|
+
});
|
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import type { AppDatabaseCompatibility } from '../database-lifecycle/appDatabaseCompatibility.js';
|
|
4
|
+
import { ViewMode } from '../types/event/index.js';
|
|
5
|
+
import {
|
|
6
|
+
getAppDatabaseRemediationActions,
|
|
7
|
+
getIntegrationRemediationActions,
|
|
8
|
+
rollUpAppEnvironmentCompatibility,
|
|
9
|
+
summarizeIntegrationConfigurations,
|
|
10
|
+
summarizeIntegrationCoverage
|
|
11
|
+
} from './appEnvironmentCompatibility.js';
|
|
12
|
+
|
|
13
|
+
const compatibleAppDatabase: AppDatabaseCompatibility = {
|
|
14
|
+
applicationId: 'application-id',
|
|
15
|
+
hasAppDatabase: true,
|
|
16
|
+
needsAttention: false,
|
|
17
|
+
overallState: 'compatible',
|
|
18
|
+
tags: []
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
describe('app Environment compatibility contract', () => {
|
|
22
|
+
it('summarizes configured integration data tags and default fallback', () => {
|
|
23
|
+
expect(
|
|
24
|
+
summarizeIntegrationConfigurations([
|
|
25
|
+
{ isDefault: false, profileNames: ['staging', 'production'] },
|
|
26
|
+
{ isDefault: false, profileNames: ['staging'] },
|
|
27
|
+
{ isDefault: true }
|
|
28
|
+
])
|
|
29
|
+
).toEqual({
|
|
30
|
+
hasDefaultConfiguration: true,
|
|
31
|
+
supportedProfileKeys: ['production', 'staging']
|
|
32
|
+
});
|
|
33
|
+
expect(summarizeIntegrationConfigurations([])).toEqual({
|
|
34
|
+
hasDefaultConfiguration: false,
|
|
35
|
+
supportedProfileKeys: []
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('marks integration gaps as warnings without blocking', () => {
|
|
40
|
+
expect(
|
|
41
|
+
rollUpAppEnvironmentCompatibility({
|
|
42
|
+
appDatabase: compatibleAppDatabase,
|
|
43
|
+
integrations: [
|
|
44
|
+
{
|
|
45
|
+
integrationId: 'integration-id',
|
|
46
|
+
integrationName: 'Warehouse',
|
|
47
|
+
mismatchedAppProfileKeys: ['production'],
|
|
48
|
+
pluginId: 'postgres',
|
|
49
|
+
supportedProfileKeys: [],
|
|
50
|
+
tags: [
|
|
51
|
+
{
|
|
52
|
+
availableModes: [ViewMode.DEPLOYED],
|
|
53
|
+
profileId: 'profile-id',
|
|
54
|
+
profileKey: 'production',
|
|
55
|
+
remediationActions: ['configure_integration', 'change_app_environment'],
|
|
56
|
+
state: 'none'
|
|
57
|
+
}
|
|
58
|
+
]
|
|
59
|
+
}
|
|
60
|
+
]
|
|
61
|
+
})
|
|
62
|
+
).toEqual({
|
|
63
|
+
isBlocking: false,
|
|
64
|
+
needsAttention: true,
|
|
65
|
+
overallState: 'warning',
|
|
66
|
+
remediationActions: ['change_app_environment', 'configure_integration']
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('keeps App Database incompatibility blocking when integrations are compatible', () => {
|
|
71
|
+
expect(
|
|
72
|
+
rollUpAppEnvironmentCompatibility({
|
|
73
|
+
appDatabase: {
|
|
74
|
+
...compatibleAppDatabase,
|
|
75
|
+
needsAttention: true,
|
|
76
|
+
overallState: 'incompatible'
|
|
77
|
+
},
|
|
78
|
+
integrations: []
|
|
79
|
+
})
|
|
80
|
+
).toEqual({
|
|
81
|
+
isBlocking: true,
|
|
82
|
+
needsAttention: true,
|
|
83
|
+
overallState: 'blocked',
|
|
84
|
+
remediationActions: []
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('does not block environment changes when the app has no App Database', () => {
|
|
89
|
+
expect(
|
|
90
|
+
rollUpAppEnvironmentCompatibility({
|
|
91
|
+
appDatabase: {
|
|
92
|
+
...compatibleAppDatabase,
|
|
93
|
+
hasAppDatabase: false,
|
|
94
|
+
needsAttention: true,
|
|
95
|
+
overallState: 'incompatible'
|
|
96
|
+
},
|
|
97
|
+
appDatabaseRemediationActions: getAppDatabaseRemediationActions('incompatible', true),
|
|
98
|
+
integrations: []
|
|
99
|
+
})
|
|
100
|
+
).toEqual({
|
|
101
|
+
isBlocking: false,
|
|
102
|
+
needsAttention: false,
|
|
103
|
+
overallState: 'compatible',
|
|
104
|
+
remediationActions: []
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('blocks a temporarily unavailable App Database, matching the server gate', () => {
|
|
109
|
+
expect(
|
|
110
|
+
rollUpAppEnvironmentCompatibility({
|
|
111
|
+
appDatabase: {
|
|
112
|
+
...compatibleAppDatabase,
|
|
113
|
+
overallState: 'temporarily_unavailable'
|
|
114
|
+
},
|
|
115
|
+
appDatabaseRemediationActions: getAppDatabaseRemediationActions('temporarily_unavailable', true),
|
|
116
|
+
integrations: []
|
|
117
|
+
})
|
|
118
|
+
).toEqual({
|
|
119
|
+
isBlocking: true,
|
|
120
|
+
needsAttention: true,
|
|
121
|
+
overallState: 'blocked',
|
|
122
|
+
remediationActions: ['retry_later']
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('keeps a temporarily unavailable integration tag a non-blocking warning', () => {
|
|
127
|
+
expect(
|
|
128
|
+
rollUpAppEnvironmentCompatibility({
|
|
129
|
+
appDatabase: compatibleAppDatabase,
|
|
130
|
+
integrations: [
|
|
131
|
+
{
|
|
132
|
+
integrationId: 'integration-id',
|
|
133
|
+
integrationName: 'Warehouse',
|
|
134
|
+
mismatchedAppProfileKeys: ['production'],
|
|
135
|
+
pluginId: 'postgres',
|
|
136
|
+
supportedProfileKeys: [],
|
|
137
|
+
tags: [
|
|
138
|
+
{
|
|
139
|
+
availableModes: [ViewMode.DEPLOYED],
|
|
140
|
+
profileId: 'profile-id',
|
|
141
|
+
profileKey: 'production',
|
|
142
|
+
remediationActions: ['retry_later'],
|
|
143
|
+
state: 'temporarily_unavailable'
|
|
144
|
+
}
|
|
145
|
+
]
|
|
146
|
+
}
|
|
147
|
+
]
|
|
148
|
+
})
|
|
149
|
+
).toEqual({
|
|
150
|
+
isBlocking: false,
|
|
151
|
+
needsAttention: true,
|
|
152
|
+
overallState: 'warning',
|
|
153
|
+
remediationActions: ['retry_later']
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it('does not surface remediations for organization tags the app does not use', () => {
|
|
158
|
+
expect(
|
|
159
|
+
rollUpAppEnvironmentCompatibility({
|
|
160
|
+
appDatabase: compatibleAppDatabase,
|
|
161
|
+
integrations: [
|
|
162
|
+
{
|
|
163
|
+
integrationId: 'integration-id',
|
|
164
|
+
integrationName: 'Warehouse',
|
|
165
|
+
mismatchedAppProfileKeys: [],
|
|
166
|
+
pluginId: 'postgres',
|
|
167
|
+
supportedProfileKeys: [],
|
|
168
|
+
tags: [
|
|
169
|
+
{
|
|
170
|
+
availableModes: [],
|
|
171
|
+
profileId: 'unused-id',
|
|
172
|
+
profileKey: 'unused',
|
|
173
|
+
remediationActions: ['change_app_environment', 'configure_integration'],
|
|
174
|
+
state: 'none'
|
|
175
|
+
}
|
|
176
|
+
]
|
|
177
|
+
}
|
|
178
|
+
]
|
|
179
|
+
})
|
|
180
|
+
).toEqual({
|
|
181
|
+
isBlocking: false,
|
|
182
|
+
needsAttention: false,
|
|
183
|
+
overallState: 'compatible',
|
|
184
|
+
remediationActions: []
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it('reports a temporary integration read failure as a non-blocking warning', () => {
|
|
189
|
+
expect(
|
|
190
|
+
rollUpAppEnvironmentCompatibility({
|
|
191
|
+
appDatabase: compatibleAppDatabase,
|
|
192
|
+
integrationReadState: 'temporarily_unavailable',
|
|
193
|
+
integrations: []
|
|
194
|
+
})
|
|
195
|
+
).toEqual({
|
|
196
|
+
isBlocking: false,
|
|
197
|
+
needsAttention: true,
|
|
198
|
+
overallState: 'warning',
|
|
199
|
+
remediationActions: ['retry_later']
|
|
200
|
+
});
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
it('deduplicates and sorts server-provided remediation actions', () => {
|
|
204
|
+
expect(
|
|
205
|
+
rollUpAppEnvironmentCompatibility({
|
|
206
|
+
appDatabase: compatibleAppDatabase,
|
|
207
|
+
integrations: [
|
|
208
|
+
{
|
|
209
|
+
integrationId: 'integration-id',
|
|
210
|
+
integrationName: 'Warehouse',
|
|
211
|
+
mismatchedAppProfileKeys: ['production'],
|
|
212
|
+
pluginId: 'postgres',
|
|
213
|
+
supportedProfileKeys: [],
|
|
214
|
+
tags: [
|
|
215
|
+
{
|
|
216
|
+
availableModes: [ViewMode.DEPLOYED],
|
|
217
|
+
profileId: 'first-profile',
|
|
218
|
+
profileKey: 'production',
|
|
219
|
+
remediationActions: ['configure_integration', 'request_environment_access'],
|
|
220
|
+
state: 'none'
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
availableModes: [ViewMode.EDITOR],
|
|
224
|
+
profileId: 'second-profile',
|
|
225
|
+
profileKey: 'staging',
|
|
226
|
+
remediationActions: ['retry_later', 'configure_integration'],
|
|
227
|
+
state: 'temporarily_unavailable'
|
|
228
|
+
}
|
|
229
|
+
]
|
|
230
|
+
}
|
|
231
|
+
]
|
|
232
|
+
}).remediationActions
|
|
233
|
+
).toEqual(['configure_integration', 'request_environment_access', 'retry_later']);
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
it('treats default-fallback tags as supported because the integration still works', () => {
|
|
237
|
+
expect(
|
|
238
|
+
summarizeIntegrationCoverage([
|
|
239
|
+
{
|
|
240
|
+
availableModes: [ViewMode.EDITOR],
|
|
241
|
+
profileId: 'staging-id',
|
|
242
|
+
profileKey: 'staging',
|
|
243
|
+
remediationActions: [],
|
|
244
|
+
state: 'exact'
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
availableModes: [ViewMode.DEPLOYED],
|
|
248
|
+
profileId: 'production-id',
|
|
249
|
+
profileKey: 'production',
|
|
250
|
+
remediationActions: [],
|
|
251
|
+
state: 'default_fallback'
|
|
252
|
+
},
|
|
253
|
+
{
|
|
254
|
+
availableModes: [],
|
|
255
|
+
profileId: 'prod-eu-id',
|
|
256
|
+
profileKey: 'prod-eu',
|
|
257
|
+
remediationActions: [],
|
|
258
|
+
state: 'exact'
|
|
259
|
+
},
|
|
260
|
+
{
|
|
261
|
+
availableModes: [ViewMode.PREVIEW],
|
|
262
|
+
profileId: 'preview-id',
|
|
263
|
+
profileKey: 'preview',
|
|
264
|
+
remediationActions: ['configure_integration'],
|
|
265
|
+
state: 'none'
|
|
266
|
+
},
|
|
267
|
+
{
|
|
268
|
+
availableModes: [],
|
|
269
|
+
profileId: 'unused-id',
|
|
270
|
+
profileKey: 'unused',
|
|
271
|
+
remediationActions: [],
|
|
272
|
+
state: 'none'
|
|
273
|
+
}
|
|
274
|
+
])
|
|
275
|
+
).toEqual({
|
|
276
|
+
mismatchedAppProfileKeys: ['preview'],
|
|
277
|
+
supportedProfileKeys: ['prod-eu', 'production', 'staging']
|
|
278
|
+
});
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
it('does not warn when an app tag uses the integration default configuration', () => {
|
|
282
|
+
expect(
|
|
283
|
+
rollUpAppEnvironmentCompatibility({
|
|
284
|
+
appDatabase: compatibleAppDatabase,
|
|
285
|
+
integrations: [
|
|
286
|
+
{
|
|
287
|
+
integrationId: 'integration-id',
|
|
288
|
+
integrationName: 'Warehouse',
|
|
289
|
+
mismatchedAppProfileKeys: [],
|
|
290
|
+
pluginId: 'postgres',
|
|
291
|
+
supportedProfileKeys: ['production'],
|
|
292
|
+
tags: [
|
|
293
|
+
{
|
|
294
|
+
availableModes: [ViewMode.DEPLOYED],
|
|
295
|
+
profileId: 'production-id',
|
|
296
|
+
profileKey: 'production',
|
|
297
|
+
remediationActions: [],
|
|
298
|
+
state: 'default_fallback'
|
|
299
|
+
}
|
|
300
|
+
]
|
|
301
|
+
}
|
|
302
|
+
]
|
|
303
|
+
})
|
|
304
|
+
).toEqual({
|
|
305
|
+
isBlocking: false,
|
|
306
|
+
needsAttention: false,
|
|
307
|
+
overallState: 'compatible',
|
|
308
|
+
remediationActions: []
|
|
309
|
+
});
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
it('returns only failure-specific integration remediation actions', () => {
|
|
313
|
+
expect(getIntegrationRemediationActions('exact', true)).toEqual([]);
|
|
314
|
+
expect(getIntegrationRemediationActions('default_fallback', true)).toEqual([]);
|
|
315
|
+
expect(getIntegrationRemediationActions('none', true)).toEqual(['change_app_environment', 'configure_integration']);
|
|
316
|
+
expect(getIntegrationRemediationActions('none', false)).toEqual(['configure_integration', 'request_environment_access']);
|
|
317
|
+
expect(getIntegrationRemediationActions('temporarily_unavailable', true)).toEqual(['retry_later']);
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
it('returns only failure-specific App Database remediation actions', () => {
|
|
321
|
+
expect(getAppDatabaseRemediationActions('compatible', true)).toEqual([]);
|
|
322
|
+
expect(getAppDatabaseRemediationActions('incompatible', true)).toEqual(['change_app_environment', 'connect_lifecycle_agent']);
|
|
323
|
+
expect(getAppDatabaseRemediationActions('incompatible', false)).toEqual(['connect_lifecycle_agent', 'request_environment_access']);
|
|
324
|
+
expect(getAppDatabaseRemediationActions('temporarily_unavailable', true)).toEqual(['retry_later']);
|
|
325
|
+
});
|
|
326
|
+
});
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import type { AppDatabaseCompatibility, AppDatabaseCompatibilityState } from '../database-lifecycle/appDatabaseCompatibility.js';
|
|
2
|
+
import type { ViewMode } from '../types/event/index.js';
|
|
3
|
+
|
|
4
|
+
export type AppEnvironmentRemediationAction =
|
|
5
|
+
| 'change_app_environment'
|
|
6
|
+
| 'configure_integration'
|
|
7
|
+
| 'connect_lifecycle_agent'
|
|
8
|
+
| 'request_environment_access'
|
|
9
|
+
| 'retry_later';
|
|
10
|
+
export const APP_ENVIRONMENT_REMEDIATION_ACTIONS: readonly AppEnvironmentRemediationAction[] = [
|
|
11
|
+
'change_app_environment',
|
|
12
|
+
'configure_integration',
|
|
13
|
+
'connect_lifecycle_agent',
|
|
14
|
+
'request_environment_access',
|
|
15
|
+
'retry_later'
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
export type IntegrationCompatibilityState = 'default_fallback' | 'exact' | 'none' | 'temporarily_unavailable';
|
|
19
|
+
export const INTEGRATION_COMPATIBILITY_STATES: readonly IntegrationCompatibilityState[] = [
|
|
20
|
+
'default_fallback',
|
|
21
|
+
'exact',
|
|
22
|
+
'none',
|
|
23
|
+
'temporarily_unavailable'
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
export type IntegrationCompatibilityReadState = 'available' | 'temporarily_unavailable';
|
|
27
|
+
export const INTEGRATION_COMPATIBILITY_READ_STATES: readonly IntegrationCompatibilityReadState[] = ['available', 'temporarily_unavailable'];
|
|
28
|
+
|
|
29
|
+
export type AppEnvironmentCompatibilityState = 'blocked' | 'compatible' | 'warning';
|
|
30
|
+
export const APP_ENVIRONMENT_COMPATIBILITY_STATES: readonly AppEnvironmentCompatibilityState[] = ['blocked', 'compatible', 'warning'];
|
|
31
|
+
|
|
32
|
+
export type IntegrationTagCompatibility = {
|
|
33
|
+
availableModes: ViewMode[];
|
|
34
|
+
profileId: string;
|
|
35
|
+
profileKey: string;
|
|
36
|
+
remediationActions: AppEnvironmentRemediationAction[];
|
|
37
|
+
state: IntegrationCompatibilityState;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export type AppEnvironmentDataTag = {
|
|
41
|
+
availableModes: ViewMode[];
|
|
42
|
+
profileId: string;
|
|
43
|
+
profileKey: string;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export type OrganizationEnvironmentDataTag = {
|
|
47
|
+
displayName: string;
|
|
48
|
+
profileId: string;
|
|
49
|
+
profileKey: string;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export type IntegrationCompatibility = {
|
|
53
|
+
integrationId: string;
|
|
54
|
+
integrationName: string;
|
|
55
|
+
mismatchedAppProfileKeys: string[];
|
|
56
|
+
pluginId: string;
|
|
57
|
+
supportedProfileKeys: string[];
|
|
58
|
+
tags: IntegrationTagCompatibility[];
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export type AppEnvironmentCompatibility = {
|
|
62
|
+
appDatabase: AppDatabaseCompatibility;
|
|
63
|
+
appDataTags: AppEnvironmentDataTag[];
|
|
64
|
+
applicationId: string;
|
|
65
|
+
canManageEnvironments: boolean;
|
|
66
|
+
integrationReadState: IntegrationCompatibilityReadState;
|
|
67
|
+
integrations: IntegrationCompatibility[];
|
|
68
|
+
isBlocking: boolean;
|
|
69
|
+
needsAttention: boolean;
|
|
70
|
+
organizationDataTags: OrganizationEnvironmentDataTag[];
|
|
71
|
+
overallState: AppEnvironmentCompatibilityState;
|
|
72
|
+
remediationActions: AppEnvironmentRemediationAction[];
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
export function summarizeIntegrationConfigurations(
|
|
76
|
+
configurations: readonly {
|
|
77
|
+
isDefault: boolean;
|
|
78
|
+
profileNames?: readonly string[];
|
|
79
|
+
}[]
|
|
80
|
+
): {
|
|
81
|
+
hasDefaultConfiguration: boolean;
|
|
82
|
+
supportedProfileKeys: string[];
|
|
83
|
+
} {
|
|
84
|
+
return {
|
|
85
|
+
hasDefaultConfiguration: configurations.some((configuration) => configuration.isDefault),
|
|
86
|
+
supportedProfileKeys: [...new Set(configurations.flatMap((configuration) => configuration.profileNames ?? []))].sort()
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function summarizeIntegrationCoverage(
|
|
91
|
+
tags: readonly IntegrationTagCompatibility[]
|
|
92
|
+
): Pick<IntegrationCompatibility, 'mismatchedAppProfileKeys' | 'supportedProfileKeys'> {
|
|
93
|
+
return {
|
|
94
|
+
mismatchedAppProfileKeys: tags
|
|
95
|
+
.filter((tag) => tag.availableModes.length > 0 && tag.state !== 'exact' && tag.state !== 'default_fallback')
|
|
96
|
+
.map((tag) => tag.profileKey)
|
|
97
|
+
.sort(),
|
|
98
|
+
supportedProfileKeys: tags
|
|
99
|
+
.filter((tag) => tag.state === 'exact' || tag.state === 'default_fallback')
|
|
100
|
+
.map((tag) => tag.profileKey)
|
|
101
|
+
.sort()
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function getIntegrationRemediationActions(
|
|
106
|
+
state: IntegrationCompatibilityState,
|
|
107
|
+
canManageEnvironments: boolean
|
|
108
|
+
): AppEnvironmentRemediationAction[] {
|
|
109
|
+
if (state === 'exact' || state === 'default_fallback') {
|
|
110
|
+
return [];
|
|
111
|
+
}
|
|
112
|
+
if (state === 'temporarily_unavailable') {
|
|
113
|
+
return ['retry_later'];
|
|
114
|
+
}
|
|
115
|
+
return canManageEnvironments
|
|
116
|
+
? ['change_app_environment', 'configure_integration']
|
|
117
|
+
: ['configure_integration', 'request_environment_access'];
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function getAppDatabaseRemediationActions(
|
|
121
|
+
state: AppDatabaseCompatibilityState,
|
|
122
|
+
canManageEnvironments: boolean
|
|
123
|
+
): AppEnvironmentRemediationAction[] {
|
|
124
|
+
if (state === 'compatible') {
|
|
125
|
+
return [];
|
|
126
|
+
}
|
|
127
|
+
if (state === 'temporarily_unavailable') {
|
|
128
|
+
return ['retry_later'];
|
|
129
|
+
}
|
|
130
|
+
return canManageEnvironments
|
|
131
|
+
? ['change_app_environment', 'connect_lifecycle_agent']
|
|
132
|
+
: ['connect_lifecycle_agent', 'request_environment_access'];
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export function rollUpAppEnvironmentCompatibility({
|
|
136
|
+
appDatabase,
|
|
137
|
+
appDatabaseRemediationActions = [],
|
|
138
|
+
integrationReadState = 'available',
|
|
139
|
+
integrations
|
|
140
|
+
}: {
|
|
141
|
+
appDatabase: AppDatabaseCompatibility;
|
|
142
|
+
appDatabaseRemediationActions?: AppEnvironmentRemediationAction[];
|
|
143
|
+
integrationReadState?: IntegrationCompatibilityReadState;
|
|
144
|
+
integrations: readonly IntegrationCompatibility[];
|
|
145
|
+
}): Pick<AppEnvironmentCompatibility, 'isBlocking' | 'needsAttention' | 'overallState' | 'remediationActions'> {
|
|
146
|
+
const integrationNeedsAttention = integrations.some((integration) =>
|
|
147
|
+
integration.tags.some((tag) => tag.state === 'temporarily_unavailable' || (tag.availableModes.length > 0 && tag.state === 'none'))
|
|
148
|
+
);
|
|
149
|
+
// The settings write only refuses App Database apps. An unserved data tag on
|
|
150
|
+
// an ordinary app is not a block, so previewing it as one would stop a change
|
|
151
|
+
// the server would accept.
|
|
152
|
+
const appDatabaseBlocks = appDatabase.hasAppDatabase && appDatabase.overallState !== 'compatible';
|
|
153
|
+
const isBlocking = appDatabaseBlocks;
|
|
154
|
+
const needsAttention = appDatabaseBlocks || integrationNeedsAttention || integrationReadState === 'temporarily_unavailable';
|
|
155
|
+
const readRemediationActions: AppEnvironmentRemediationAction[] =
|
|
156
|
+
integrationReadState === 'temporarily_unavailable' ? ['retry_later'] : [];
|
|
157
|
+
const remediationActions = [
|
|
158
|
+
...new Set([
|
|
159
|
+
...(appDatabaseBlocks ? appDatabaseRemediationActions : []),
|
|
160
|
+
...readRemediationActions,
|
|
161
|
+
...integrations.flatMap((integration) =>
|
|
162
|
+
integration.tags.flatMap((tag) => (tag.availableModes.length > 0 ? tag.remediationActions : []))
|
|
163
|
+
)
|
|
164
|
+
])
|
|
165
|
+
].sort();
|
|
166
|
+
|
|
167
|
+
return {
|
|
168
|
+
isBlocking,
|
|
169
|
+
needsAttention,
|
|
170
|
+
overallState: isBlocking ? 'blocked' : needsAttention ? 'warning' : 'compatible',
|
|
171
|
+
remediationActions
|
|
172
|
+
};
|
|
173
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { BadGatewayError, GatewayTimeoutError, InternalServerError, RetryableHttpError, ServiceUnavailableError } from './http.js';
|
|
4
|
+
|
|
5
|
+
describe('GatewayTimeoutError', () => {
|
|
6
|
+
it('reports 504 so a caller can tell a slow upstream from a broken server', () => {
|
|
7
|
+
const error = new GatewayTimeoutError('upstream took too long');
|
|
8
|
+
|
|
9
|
+
expect(error.status).toBe(504);
|
|
10
|
+
expect(error.message).toBe('upstream took too long');
|
|
11
|
+
});
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
describe('retry hints', () => {
|
|
15
|
+
const retryableErrors: [number, (retryAfterSeconds?: number) => RetryableHttpError][] = [
|
|
16
|
+
[502, (retryAfterSeconds) => new BadGatewayError('upstream refused', retryAfterSeconds)],
|
|
17
|
+
[503, (retryAfterSeconds) => new ServiceUnavailableError('upstream at capacity', retryAfterSeconds)],
|
|
18
|
+
[504, (retryAfterSeconds) => new GatewayTimeoutError('upstream took too long', retryAfterSeconds)]
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
it.each(retryableErrors)('a %i carries the seconds a client should wait', (status, build) => {
|
|
22
|
+
expect(build(7)).toMatchObject({ status, retryAfterSeconds: 7 });
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it.each(retryableErrors)('a %i without a hint leaves the wait unspecified', (_status, build) => {
|
|
26
|
+
expect(build().retryAfterSeconds).toBeUndefined();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('never invites a retry of an outright server fault', () => {
|
|
30
|
+
expect(new InternalServerError('it broke')).not.toBeInstanceOf(RetryableHttpError);
|
|
31
|
+
});
|
|
32
|
+
});
|