@jskit-ai/connectors-core 0.2.3 → 0.2.5
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/README.md +6 -1
- package/docs/setup-command.md +25 -25
- package/locales/en.json +1 -1
- package/package.json +4 -3
- package/src/server/ConnectorsFeature.js +65 -18
- package/src/server/connectionCredentials.js +129 -0
- package/src/server/connectionService.js +104 -319
- package/src/server/credentialProtection.js +31 -20
- package/src/server/environmentReferences.js +10 -1
- package/src/server/errors.js +27 -7
- package/src/server/fileConnectionStore.js +10 -0
- package/src/server/index.js +1 -1
- package/src/server/knexConnectionStore.js +13 -2
- package/src/server/providerRequests.js +291 -0
- package/src/server/types.js +150 -0
- package/src/shared/configuration.js +33 -16
- package/test/connectionService.test.js +50 -8
- package/test/serviceAccount.test.js +4 -2
- package/test/setupCommand.test.js +9 -8
- /package/docs/{online-setup.md → application-setup.md} +0 -0
|
@@ -92,17 +92,23 @@ const FIELD_ERRORS = Object.freeze({
|
|
|
92
92
|
clientId: { code: "connectors.configuration.clientId", message: "Check this provider's client identifier." },
|
|
93
93
|
requiredScopes: { code: "connectors.configuration.requiredScopes", message: "Include this provider's required permissions." },
|
|
94
94
|
scopeUnsupported: { code: "connectors.configuration.scopeUnsupported", message: "A selected permission is not supported by this provider configuration." },
|
|
95
|
-
scopeRequired: { code: "connectors.configuration.scopeRequired", message: "Select at least one permission." }
|
|
95
|
+
scopeRequired: { code: "connectors.configuration.scopeRequired", message: "Select at least one permission." },
|
|
96
|
+
secretReference: { code: "connectors.configuration.secretReference", message: "Use a reference such as env:VARIABLE_NAME." }
|
|
96
97
|
});
|
|
97
98
|
|
|
98
|
-
// A schema validation error's field errors with the codes the validator gave them.
|
|
99
|
+
// A schema validation error's field errors with the codes the validator gave them. A
|
|
100
|
+
// credential reference that fails secretReference's pattern says what a reference looks like.
|
|
99
101
|
function schemaFieldErrors(error, fallback, prefix = "") {
|
|
100
102
|
const messages = {};
|
|
101
103
|
const codes = {};
|
|
102
104
|
for (const [field, message] of Object.entries(error.fieldErrors || fallback)) {
|
|
103
|
-
messages[`${prefix}${field}`] = message;
|
|
104
105
|
const coded = error.fieldErrorCodes?.[field];
|
|
105
|
-
if (coded
|
|
106
|
+
if (coded?.code === "kernel.validation.field.pattern" && coded.params.pattern === secretReference.pattern) {
|
|
107
|
+
messages[`${prefix}${field}`] = FIELD_ERRORS.secretReference;
|
|
108
|
+
} else {
|
|
109
|
+
messages[`${prefix}${field}`] = message;
|
|
110
|
+
if (coded) codes[`${prefix}${field}`] = coded;
|
|
111
|
+
}
|
|
106
112
|
}
|
|
107
113
|
return { messages, codes };
|
|
108
114
|
}
|
|
@@ -116,8 +122,8 @@ const MESSAGE_KEY_SOURCE = Symbol.for("@jskit-ai/connectors-core.messageKeySourc
|
|
|
116
122
|
|
|
117
123
|
// Provider definitions carry message keys, which connectors-web translates. Code that
|
|
118
124
|
// shows a definition's texts itself gets the same definitions with every key (and every
|
|
119
|
-
// { key, params } a function returns) rendered to the catalog's English
|
|
120
|
-
//
|
|
125
|
+
// { key, params } a function returns) rendered to the catalog's English text. Each
|
|
126
|
+
// English object keeps its keyed original under MESSAGE_KEY_SOURCE.
|
|
121
127
|
function createEnglishProviderTexts(catalog) {
|
|
122
128
|
const rendered = new WeakMap();
|
|
123
129
|
const englishMessage = (key, params) => {
|
|
@@ -168,25 +174,31 @@ function providerGrantTypes(provider) {
|
|
|
168
174
|
}
|
|
169
175
|
|
|
170
176
|
function getProviderSettingsSchema(provider, settings = {}) {
|
|
177
|
+
// readable-check: allow method-probe - data: a provider definition gives its settings schema as a schema or as a function of the settings
|
|
171
178
|
return typeof provider.settingsSchema === "function" ? provider.settingsSchema(settings) : provider.settingsSchema;
|
|
172
179
|
}
|
|
173
180
|
|
|
174
181
|
function getProviderAuthenticationMethods(provider, settings = {}) {
|
|
175
|
-
|
|
182
|
+
// readable-check: allow method-probe - optional operation: authenticationMethodsForSettings is an optional hook of the provider definition
|
|
183
|
+
return typeof provider.authenticationMethodsForSettings === "function" ? provider.authenticationMethodsForSettings(settings) : provider.authenticationMethods;
|
|
176
184
|
}
|
|
177
185
|
|
|
178
186
|
function getProviderAccountModes(provider, settings = {}) {
|
|
179
|
-
|
|
187
|
+
// readable-check: allow method-probe - optional operation: accountModesForSettings is an optional hook of the provider definition
|
|
188
|
+
return typeof provider.accountModesForSettings === "function" ? provider.accountModesForSettings(settings) : provider.accountModes;
|
|
180
189
|
}
|
|
181
190
|
|
|
182
191
|
function getProviderScopes(provider, settings = {}, grantType = DEFAULT_GRANT_TYPE, authenticationMethod) {
|
|
183
|
-
|
|
184
|
-
|
|
192
|
+
// readable-check: allow method-probe - optional operation: scopesForGrantType is an optional hook of the provider definition
|
|
193
|
+
const scopes = typeof provider.scopesForGrantType === "function" ? provider.scopesForGrantType(grantType, settings)
|
|
194
|
+
// readable-check: allow method-probe - optional operation: scopesForSettings is an optional hook of the provider definition
|
|
195
|
+
: typeof provider.scopesForSettings === "function" ? provider.scopesForSettings(settings) : provider.scopes;
|
|
185
196
|
return authenticationMethod ? scopes.filter(scope => !scope.authenticationMethods || scope.authenticationMethods.includes(authenticationMethod)) : scopes;
|
|
186
197
|
}
|
|
187
198
|
|
|
188
199
|
function getProviderClientAuthenticationMethods(provider, grantType = DEFAULT_GRANT_TYPE, settings = {}) {
|
|
189
200
|
const methods = provider.oauthClientAuthenticationMethods;
|
|
201
|
+
// readable-check: allow method-probe - data: a provider definition gives its client authentication methods as a list or as a function of the grant type
|
|
190
202
|
return typeof methods === "function" ? methods(grantType, settings) : methods || [DEFAULT_TOKEN_ENDPOINT_AUTH_METHOD];
|
|
191
203
|
}
|
|
192
204
|
|
|
@@ -194,12 +206,15 @@ function getProviderClientAuthenticationMethods(provider, grantType = DEFAULT_GR
|
|
|
194
206
|
function getProviderSetup(provider, settings = {}, authenticationMethod) {
|
|
195
207
|
const setup = provider.setup || {};
|
|
196
208
|
return {
|
|
209
|
+
// readable-check: allow optional-call - data: a provider definition's setup may give its URL for the settings
|
|
197
210
|
url: setup.urlForSettings?.(settings, authenticationMethod) || setup.urlByAuthentication?.[authenticationMethod] || setup.url,
|
|
211
|
+
// readable-check: allow optional-call - data: a provider definition's setup may give its steps for the settings
|
|
198
212
|
steps: setup.stepsForSettings?.(settings, authenticationMethod) || setup.stepsByAuthentication?.[authenticationMethod] || setup.steps
|
|
199
213
|
};
|
|
200
214
|
}
|
|
201
215
|
|
|
202
216
|
function isProviderConfigurationOnly(provider, settings = {}) {
|
|
217
|
+
// readable-check: allow optional-call - data: a provider definition may say per settings whether it is configuration only
|
|
203
218
|
return Boolean(provider.configurationOnly || provider.configurationOnlyForSettings?.(settings));
|
|
204
219
|
}
|
|
205
220
|
|
|
@@ -296,15 +311,16 @@ function validateIntegrationConfiguration(input, { providers = [], allowUnknownP
|
|
|
296
311
|
Object.assign(fieldErrorCodes, codes);
|
|
297
312
|
}
|
|
298
313
|
}
|
|
314
|
+
const settings = integration.settings || {};
|
|
299
315
|
for (const field of provider.settingsFields || []) {
|
|
300
|
-
if (field.authenticationMethods && !field.authenticationMethods.includes(auth.method) && Object.hasOwn(
|
|
316
|
+
if (field.authenticationMethods && !field.authenticationMethods.includes(auth.method) && Object.hasOwn(settings, field.name)) {
|
|
301
317
|
fieldErrors[`${path}.settings.${field.name}`] = FIELD_ERRORS.otherCredentialModeSetting;
|
|
302
318
|
}
|
|
303
319
|
}
|
|
304
|
-
if (!getProviderAccountModes(provider,
|
|
320
|
+
if (!getProviderAccountModes(provider, settings).includes(integration.accountMode)) {
|
|
305
321
|
fieldErrors[`${path}.accountMode`] = FIELD_ERRORS.accountModeUnsupported;
|
|
306
322
|
}
|
|
307
|
-
if (!getProviderAuthenticationMethods(provider,
|
|
323
|
+
if (!getProviderAuthenticationMethods(provider, settings).includes(auth.method)) {
|
|
308
324
|
fieldErrors[`${path}.authentication.method`] = FIELD_ERRORS.credentialModeUnsupported;
|
|
309
325
|
}
|
|
310
326
|
if (auth.method === "oauth2" && registration?.source === "own") {
|
|
@@ -312,16 +328,17 @@ function validateIntegrationConfiguration(input, { providers = [], allowUnknownP
|
|
|
312
328
|
if (!providerGrantTypes(provider).includes(grantType)) {
|
|
313
329
|
fieldErrors[`${registrationPath}.grantType`] = FIELD_ERRORS.oauthFlowUnsupported;
|
|
314
330
|
}
|
|
315
|
-
const methods = getProviderClientAuthenticationMethods(provider, grantType,
|
|
331
|
+
const methods = getProviderClientAuthenticationMethods(provider, grantType, settings);
|
|
316
332
|
if (!methods.includes(registrationClientAuthentication(registration))) {
|
|
317
333
|
fieldErrors[`${registrationPath}.tokenEndpointAuthMethod`] = FIELD_ERRORS.clientAuthenticationUnsupported;
|
|
318
334
|
}
|
|
319
|
-
|
|
335
|
+
// readable-check: allow method-probe - optional operation: validateClientId is an optional hook of the provider definition
|
|
336
|
+
if (registration.clientId && typeof provider.validateClientId === "function") {
|
|
320
337
|
const result = provider.validateClientId(registration.clientId);
|
|
321
338
|
if (result !== true) fieldErrors[`${registrationPath}.clientId`] = result || FIELD_ERRORS.clientId;
|
|
322
339
|
}
|
|
323
340
|
}
|
|
324
|
-
const scopes = getProviderScopes(provider,
|
|
341
|
+
const scopes = getProviderScopes(provider, settings, grantType, auth.method);
|
|
325
342
|
const allowedScopes = new Set(scopes.map((scope) => scope.value));
|
|
326
343
|
if (scopes.some((scope) => scope.required && !integration.scopes.includes(scope.value))) {
|
|
327
344
|
fieldErrors[`${path}.scopes`] = FIELD_ERRORS.requiredScopes;
|
|
@@ -5,11 +5,15 @@ import assert from "node:assert/strict";
|
|
|
5
5
|
import test from "node:test";
|
|
6
6
|
import Fastify from "fastify";
|
|
7
7
|
import { createCapabilityRuntime, defineProvider } from "@jskit-ai/kernel/shared/capabilities";
|
|
8
|
-
import {
|
|
8
|
+
import { ActionProvider } from "@jskit-ai/kernel/server/actions";
|
|
9
|
+
import { EventProvider } from "@jskit-ai/kernel/server/runtime";
|
|
10
|
+
import { silentLogger } from "@jskit-ai/kernel/server/runtime/logger";
|
|
9
11
|
import { googleCalendarProvider } from "../../connector-google-calendar/src/server/provider.js";
|
|
10
12
|
import { createConnectionService, createConnectorsFeature, createEnvironmentReferenceResolver } from "../src/server/index.js";
|
|
11
13
|
import { integrationsSchema, parseIntegrationConfiguration, validateIntegrationConfiguration } from "../src/shared/configuration.js";
|
|
12
14
|
import { clickhouseProvider } from "../../connectors-catalog/src/server/clickhouse.js";
|
|
15
|
+
import { algoliaDefinition } from "../../connectors-catalog/src/shared/algolia.js";
|
|
16
|
+
import { firebaseCloudMessagingDefinition, firebaseMessagingScope } from "../../connectors-catalog/src/shared/firebase-cloud-messaging.js";
|
|
13
17
|
|
|
14
18
|
const listScope = "https://www.googleapis.com/auth/calendar.calendarlist.readonly";
|
|
15
19
|
const eventsScope = "https://www.googleapis.com/auth/calendar.events.readonly";
|
|
@@ -111,8 +115,8 @@ test("the ordinary credential-free Feature action verifies without resolving ref
|
|
|
111
115
|
return Response.json({ meta: [], data: [{ ok: 1, user: "default" }], rows: 1 });
|
|
112
116
|
}
|
|
113
117
|
};
|
|
114
|
-
const runtime = createCapabilityRuntime({ providers: [
|
|
115
|
-
|
|
118
|
+
const runtime = createCapabilityRuntime({ inputs: { "runtime.logger": silentLogger }, providers: [
|
|
119
|
+
EventProvider, ActionProvider, createConnectorsFeature(options),
|
|
116
120
|
defineProvider({ id: "test.credential-free-observer", requires: { catalogue: "runtime.actions" }, setup({ catalogue }) { actions = catalogue; } })
|
|
117
121
|
] });
|
|
118
122
|
await runtime.start();
|
|
@@ -395,7 +399,7 @@ test("cancelling a service token exchange preserves the previous grant without r
|
|
|
395
399
|
test("the client credentials Feature action uses the ordinary application policy and runtime", async () => {
|
|
396
400
|
const { options } = serviceAccountSetup();
|
|
397
401
|
let actions;
|
|
398
|
-
const runtime = createCapabilityRuntime({ providers: [
|
|
402
|
+
const runtime = createCapabilityRuntime({ inputs: { "runtime.logger": silentLogger }, providers: [EventProvider, ActionProvider, createConnectorsFeature(options),
|
|
399
403
|
defineProvider({ id: "test.service-observer", requires: { catalogue: "runtime.actions" }, setup({ catalogue }) { actions = catalogue; } })] });
|
|
400
404
|
await runtime.start();
|
|
401
405
|
try {
|
|
@@ -487,6 +491,44 @@ test("reference fields reject pasted web URLs while preserving environment and c
|
|
|
487
491
|
}
|
|
488
492
|
});
|
|
489
493
|
|
|
494
|
+
function assertReferenceFieldError(input, providers, field) {
|
|
495
|
+
assert.throws(() => validateIntegrationConfiguration(input, { providers }), (error) => {
|
|
496
|
+
assert.equal(error.fieldErrors[field], "Use a reference such as env:VARIABLE_NAME.", field);
|
|
497
|
+
assert.deepEqual(error.fieldErrorCodes[field], { code: "connectors.configuration.secretReference", params: {} }, field);
|
|
498
|
+
return true;
|
|
499
|
+
});
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
test("a secretRef that is not a reference says to use a reference such as env:VARIABLE_NAME", () => {
|
|
503
|
+
for (const value of ["https://x", "plain"]) {
|
|
504
|
+
assertReferenceFieldError(databaseConfiguration({ method: "api-key", secretRef: value }), [clickhouseProvider], "integrations.database.authentication.secretRef");
|
|
505
|
+
}
|
|
506
|
+
});
|
|
507
|
+
|
|
508
|
+
test("a registration clientSecretRef that is not a reference says to use a reference such as env:VARIABLE_NAME", () => {
|
|
509
|
+
for (const value of ["https://x", "plain"]) {
|
|
510
|
+
const input = configuration();
|
|
511
|
+
input.registrations.google.clientSecretRef = value;
|
|
512
|
+
assertReferenceFieldError(input, [googleCalendarProvider], "registrations.google.clientSecretRef");
|
|
513
|
+
}
|
|
514
|
+
});
|
|
515
|
+
|
|
516
|
+
test("a provider setting's reference says the same, while its other pattern failures keep the kernel's sentence", () => {
|
|
517
|
+
const input = { schemaVersion: 1, registrations: {}, integrations: {
|
|
518
|
+
search: { provider: "algolia", accountMode: "shared", scopes: [], authentication: { method: "api-key", secretRef: "env:ALGOLIA_KEY" },
|
|
519
|
+
settings: { applicationId: "APP1", publicApiKeyRef: "raw-search-key" } },
|
|
520
|
+
push: { provider: "firebase-cloud-messaging", accountMode: "shared", scopes: [firebaseMessagingScope], authentication: { method: "service-account", secretRef: "env:FCM" },
|
|
521
|
+
settings: { projectId: "push-project", clientMode: "web", apiKey: "not-a-key" } }
|
|
522
|
+
} };
|
|
523
|
+
const providers = [algoliaDefinition, firebaseCloudMessagingDefinition];
|
|
524
|
+
assertReferenceFieldError(input, providers, "integrations.search.settings.publicApiKeyRef");
|
|
525
|
+
assert.throws(() => validateIntegrationConfiguration(input, { providers }), (error) => {
|
|
526
|
+
assert.equal(error.fieldErrors["integrations.push.settings.apiKey"], "Value does not match the required pattern.");
|
|
527
|
+
assert.equal(error.fieldErrorCodes["integrations.push.settings.apiKey"].code, "kernel.validation.field.pattern");
|
|
528
|
+
return true;
|
|
529
|
+
});
|
|
530
|
+
});
|
|
531
|
+
|
|
490
532
|
test("the exported configuration contract enforces reference constraints for every credential field", async (t) => {
|
|
491
533
|
const app = Fastify({ ajv: { plugins: [(ajv) => ajv.addKeyword("x-json-rest-schema")] } });
|
|
492
534
|
t.after(() => app.close());
|
|
@@ -828,8 +870,8 @@ test("configuration rejects gateway registrations before constructing a runtime"
|
|
|
828
870
|
test("the Feature exposes ordinary JSKIT actions with application authorization", async () => {
|
|
829
871
|
const { options } = setup();
|
|
830
872
|
let actions;
|
|
831
|
-
const runtime = createCapabilityRuntime({ providers: [
|
|
832
|
-
|
|
873
|
+
const runtime = createCapabilityRuntime({ inputs: { "runtime.logger": silentLogger }, providers: [
|
|
874
|
+
EventProvider, ActionProvider, createConnectorsFeature(options),
|
|
833
875
|
defineProvider({
|
|
834
876
|
id: "test.observer", requires: { catalogue: "runtime.actions" },
|
|
835
877
|
setup({ catalogue }) { actions = catalogue; }
|
|
@@ -997,8 +1039,8 @@ test("JSON-body credentials replace supplied values, preserve input and reach th
|
|
|
997
1039
|
}
|
|
998
1040
|
};
|
|
999
1041
|
let actions;
|
|
1000
|
-
const runtime = createCapabilityRuntime({ providers: [
|
|
1001
|
-
|
|
1042
|
+
const runtime = createCapabilityRuntime({ inputs: { "runtime.logger": silentLogger }, providers: [
|
|
1043
|
+
EventProvider, ActionProvider, createConnectorsFeature(options),
|
|
1002
1044
|
defineProvider({ id: "test.body-observer", requires: { catalogue: "runtime.actions" }, setup({ catalogue }) { actions = catalogue; } })
|
|
1003
1045
|
] });
|
|
1004
1046
|
await runtime.start();
|
|
@@ -5,7 +5,9 @@ import { tmpdir } from "node:os";
|
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import { createSchema } from "json-rest-schema";
|
|
7
7
|
import { createCapabilityRuntime, defineProvider } from "@jskit-ai/kernel/shared/capabilities";
|
|
8
|
-
import {
|
|
8
|
+
import { ActionProvider } from "@jskit-ai/kernel/server/actions";
|
|
9
|
+
import { EventProvider } from "@jskit-ai/kernel/server/runtime";
|
|
10
|
+
import { silentLogger } from "@jskit-ai/kernel/server/runtime/logger";
|
|
9
11
|
import { createConnectionService, createConnectorsFeature } from "../src/server/index.js";
|
|
10
12
|
import { ConnectorError } from "../src/server/errors.js";
|
|
11
13
|
import { createFileConnectionStore, createCredentialProtection } from "../src/server/fileStorage.js";
|
|
@@ -182,7 +184,7 @@ test("service grants honor cancellation and deadlines without replay or partial
|
|
|
182
184
|
|
|
183
185
|
test("the service-account Feature action uses the same application policy and verified runtime", async (t) => {
|
|
184
186
|
const f = await fixture(t); let actions;
|
|
185
|
-
const runtime = createCapabilityRuntime({ providers: [
|
|
187
|
+
const runtime = createCapabilityRuntime({ inputs: { "runtime.logger": silentLogger }, providers: [EventProvider, ActionProvider, createConnectorsFeature(f.options),
|
|
186
188
|
defineProvider({ id: "test.service-observer", requires: { catalogue: "runtime.actions" }, setup({ catalogue }) { actions = catalogue; } })] });
|
|
187
189
|
await runtime.start();
|
|
188
190
|
try {
|
|
@@ -16,9 +16,10 @@ const guide = await readFile(new URL("../docs/setup-command.md", import.meta.url
|
|
|
16
16
|
const source = guide.match(/```js\n([\s\S]*?)\n```/u)?.[1];
|
|
17
17
|
assert.ok(source, "The setup guide must contain its dispatch example.");
|
|
18
18
|
const dispatchSetup = new Function(`${source}\nreturn dispatchSetup;`)();
|
|
19
|
+
const protocol = "example-host.integration-setup.command.v1";
|
|
19
20
|
const scope = "https://www.googleapis.com/auth/calendar.calendarlist.readonly";
|
|
20
21
|
const callback = "http://127.0.0.1:8080/integrations/google/callback";
|
|
21
|
-
const context = { applicationId: "
|
|
22
|
+
const context = { applicationId: "bookings-dev", subjectId: "business-account" };
|
|
22
23
|
|
|
23
24
|
async function fixture(t) {
|
|
24
25
|
const directory = await mkdtemp(path.join(tmpdir(), "setup-composition-"));
|
|
@@ -72,9 +73,9 @@ async function fixture(t) {
|
|
|
72
73
|
}
|
|
73
74
|
});
|
|
74
75
|
const dispatch = async (operation, integrationId, extra = {}, identity = context) => {
|
|
75
|
-
const result = await dispatchSetup({ protocol
|
|
76
|
+
const result = await dispatchSetup({ protocol,
|
|
76
77
|
requestId: "fixture-request", operation, integrationId, ...extra },
|
|
77
|
-
{ configuration, connections: restart(), context: identity });
|
|
78
|
+
{ protocol, configuration, connections: restart(), context: identity });
|
|
78
79
|
assert.equal(result.requestId, "fixture-request");
|
|
79
80
|
assert.equal(JSON.stringify(result).includes("private-"), false);
|
|
80
81
|
return result;
|
|
@@ -123,14 +124,14 @@ try {
|
|
|
123
124
|
return Response.json({ object: "list", data: [], has_more: false });
|
|
124
125
|
}
|
|
125
126
|
});
|
|
126
|
-
const result = await dispatchSetup(JSON.parse(input), { configuration, connections, context });
|
|
127
|
+
const result = await dispatchSetup(JSON.parse(input), { protocol: ${JSON.stringify(protocol)}, configuration, connections, context });
|
|
127
128
|
process.stdout.write(JSON.stringify(result) + "\\n");
|
|
128
129
|
} catch {
|
|
129
130
|
process.stderr.write("Integration setup failed.\\n");
|
|
130
131
|
process.exitCode = 1;
|
|
131
132
|
}
|
|
132
133
|
`);
|
|
133
|
-
const request = { protocol
|
|
134
|
+
const request = { protocol, requestId: "cli-request", integrationId: "mail" };
|
|
134
135
|
for (const [operation, key, applicationId, expected, allowCheck] of [
|
|
135
136
|
["status", "MISSING", "first-app", "unconfigured", false],
|
|
136
137
|
["connect", "MISSING", "first-app", "unconfigured", false],
|
|
@@ -220,7 +221,7 @@ test("moving application state preserves grants and refresh while invalidating c
|
|
|
220
221
|
const moved = `${directory}-relocated`;
|
|
221
222
|
t.after(() => rm(moved, { recursive: true, force: true }));
|
|
222
223
|
await rename(directory, moved);
|
|
223
|
-
env.CALLBACK = "https://
|
|
224
|
+
env.CALLBACK = "https://bookings.example/integrations/google/callback";
|
|
224
225
|
const relocated = restart(moved);
|
|
225
226
|
const requestCount = requests.length;
|
|
226
227
|
assert.equal(await relocated.resumeAuthorization(input), null);
|
|
@@ -235,7 +236,7 @@ test("moving application state preserves grants and refresh while invalidating c
|
|
|
235
236
|
"oauth2.googleapis.com", "www.googleapis.com"
|
|
236
237
|
]);
|
|
237
238
|
assert.equal((await restart(moved).status(input)).status, "connected");
|
|
238
|
-
const otherEnvironment = { ...context, applicationId: "
|
|
239
|
+
const otherEnvironment = { ...context, applicationId: "bookings-production" };
|
|
239
240
|
assert.equal((await relocated.status({ ...input, context: otherEnvironment })).status, "disconnected");
|
|
240
241
|
await relocated.disconnect({ ...input, context: otherEnvironment });
|
|
241
242
|
assert.equal((await relocated.status(input)).status, "connected");
|
|
@@ -259,7 +260,7 @@ test("documented OAuth setup resumes across restarts, preserves a grant on cance
|
|
|
259
260
|
const callbackUrl = `${callback}?code=fixture-code&state=${pending.attemptId}`;
|
|
260
261
|
await restart().completeAuthorization({ context, integrationId: "calendar", callbackUrl });
|
|
261
262
|
assert.equal((await dispatch("status", "calendar")).status, "connected");
|
|
262
|
-
env.CALLBACK = "https://
|
|
263
|
+
env.CALLBACK = "https://bookings.example/integrations/google/callback";
|
|
263
264
|
assert.equal((await dispatch("status", "calendar")).callbackUrl, env.CALLBACK);
|
|
264
265
|
env.CALLBACK = callback;
|
|
265
266
|
const replacement = await dispatch("connect", "calendar");
|
|
File without changes
|