latchkey 2.6.1 → 2.7.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/README.md +50 -1
- package/dist/scripts/recordBrowserSession.js +3 -3
- package/dist/scripts/recordBrowserSession.js.map +1 -1
- package/dist/src/{apiCredentials.d.ts → apiCredentials/base.d.ts} +6 -6
- package/dist/src/apiCredentials/base.d.ts.map +1 -0
- package/dist/src/{apiCredentials.js → apiCredentials/base.js} +5 -5
- package/dist/src/apiCredentials/base.js.map +1 -0
- package/dist/src/{apiCredentialsSerialization.d.ts → apiCredentials/serialization.d.ts} +5 -5
- package/dist/src/apiCredentials/serialization.d.ts.map +1 -0
- package/dist/src/{apiCredentialsSerialization.js → apiCredentials/serialization.js} +9 -9
- package/dist/src/apiCredentials/serialization.js.map +1 -0
- package/dist/src/{apiCredentialStore.d.ts → apiCredentials/store.d.ts} +3 -3
- package/dist/src/apiCredentials/store.d.ts.map +1 -0
- package/dist/src/{apiCredentialStore.js → apiCredentials/store.js} +2 -2
- package/dist/src/apiCredentials/store.js.map +1 -0
- package/dist/src/apiCredentials/utils.d.ts +13 -0
- package/dist/src/apiCredentials/utils.d.ts.map +1 -0
- package/dist/src/apiCredentials/utils.js +27 -0
- package/dist/src/apiCredentials/utils.js.map +1 -0
- package/dist/src/cli.js +42 -39
- package/dist/src/cli.js.map +1 -1
- package/dist/src/cliCommands.d.ts +5 -2
- package/dist/src/cliCommands.d.ts.map +1 -1
- package/dist/src/cliCommands.js +243 -190
- package/dist/src/cliCommands.js.map +1 -1
- package/dist/src/config.d.ts +32 -2
- package/dist/src/config.d.ts.map +1 -1
- package/dist/src/config.js +107 -20
- package/dist/src/config.js.map +1 -1
- package/dist/src/configDataStore.d.ts +44 -0
- package/dist/src/configDataStore.d.ts.map +1 -1
- package/dist/src/configDataStore.js +27 -0
- package/dist/src/configDataStore.js.map +1 -1
- package/dist/src/curl.d.ts +41 -8
- package/dist/src/curl.d.ts.map +1 -1
- package/dist/src/curl.js +80 -75
- package/dist/src/curl.js.map +1 -1
- package/dist/src/curlInjection.d.ts +46 -0
- package/dist/src/curlInjection.d.ts.map +1 -0
- package/dist/src/curlInjection.js +99 -0
- package/dist/src/curlInjection.js.map +1 -0
- package/dist/src/errorMessages.d.ts +14 -0
- package/dist/src/errorMessages.d.ts.map +1 -0
- package/dist/src/errorMessages.js +22 -0
- package/dist/src/errorMessages.js.map +1 -0
- package/dist/src/gateway/client.d.ts +32 -0
- package/dist/src/gateway/client.d.ts.map +1 -0
- package/dist/src/gateway/client.js +89 -0
- package/dist/src/gateway/client.js.map +1 -0
- package/dist/src/gateway/gatewayEndpoint.d.ts +43 -0
- package/dist/src/gateway/gatewayEndpoint.d.ts.map +1 -0
- package/dist/src/gateway/gatewayEndpoint.js +297 -0
- package/dist/src/gateway/gatewayEndpoint.js.map +1 -0
- package/dist/src/gateway/latchkeyEndpoint.d.ts +105 -0
- package/dist/src/gateway/latchkeyEndpoint.d.ts.map +1 -0
- package/dist/src/gateway/latchkeyEndpoint.js +144 -0
- package/dist/src/gateway/latchkeyEndpoint.js.map +1 -0
- package/dist/src/gateway/server.d.ts +20 -0
- package/dist/src/gateway/server.d.ts.map +1 -0
- package/dist/src/gateway/server.js +90 -0
- package/dist/src/gateway/server.js.map +1 -0
- package/dist/src/index.d.ts +4 -4
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +5 -5
- package/dist/src/index.js.map +1 -1
- package/dist/src/permissions.d.ts.map +1 -1
- package/dist/src/permissions.js +4 -2
- package/dist/src/permissions.js.map +1 -1
- package/dist/src/{registry.d.ts → serviceRegistry.d.ts} +4 -4
- package/dist/src/serviceRegistry.d.ts.map +1 -0
- package/dist/src/{registry.js → serviceRegistry.js} +4 -4
- package/dist/src/serviceRegistry.js.map +1 -0
- package/dist/src/services/aws.d.ts +2 -2
- package/dist/src/services/aws.d.ts.map +1 -1
- package/dist/src/services/aws.js +17 -10
- package/dist/src/services/aws.js.map +1 -1
- package/dist/src/services/core/base.d.ts +2 -2
- package/dist/src/services/core/base.d.ts.map +1 -1
- package/dist/src/services/core/base.js +3 -3
- package/dist/src/services/core/base.js.map +1 -1
- package/dist/src/services/core/registered.d.ts +2 -2
- package/dist/src/services/core/registered.d.ts.map +1 -1
- package/dist/src/services/core/registered.js +2 -2
- package/dist/src/services/core/registered.js.map +1 -1
- package/dist/src/services/discord.d.ts +1 -1
- package/dist/src/services/discord.d.ts.map +1 -1
- package/dist/src/services/discord.js +1 -1
- package/dist/src/services/discord.js.map +1 -1
- package/dist/src/services/dropbox.d.ts +1 -1
- package/dist/src/services/dropbox.d.ts.map +1 -1
- package/dist/src/services/dropbox.js +1 -1
- package/dist/src/services/dropbox.js.map +1 -1
- package/dist/src/services/github.d.ts +1 -1
- package/dist/src/services/github.d.ts.map +1 -1
- package/dist/src/services/github.js +1 -1
- package/dist/src/services/github.js.map +1 -1
- package/dist/src/services/google/base.d.ts +2 -2
- package/dist/src/services/google/base.d.ts.map +1 -1
- package/dist/src/services/google/base.js +3 -3
- package/dist/src/services/google/base.js.map +1 -1
- package/dist/src/services/google/directions.d.ts +1 -1
- package/dist/src/services/google/directions.d.ts.map +1 -1
- package/dist/src/services/linear.d.ts +1 -1
- package/dist/src/services/linear.d.ts.map +1 -1
- package/dist/src/services/linear.js +1 -1
- package/dist/src/services/linear.js.map +1 -1
- package/dist/src/services/notion.d.ts +1 -1
- package/dist/src/services/notion.d.ts.map +1 -1
- package/dist/src/services/notion.js +1 -1
- package/dist/src/services/notion.js.map +1 -1
- package/dist/src/services/sentry.d.ts +2 -2
- package/dist/src/services/sentry.d.ts.map +1 -1
- package/dist/src/services/sentry.js +6 -3
- package/dist/src/services/sentry.js.map +1 -1
- package/dist/src/services/slack.d.ts +3 -3
- package/dist/src/services/slack.d.ts.map +1 -1
- package/dist/src/services/slack.js +5 -5
- package/dist/src/services/slack.js.map +1 -1
- package/dist/src/services/telegram.d.ts +2 -2
- package/dist/src/services/telegram.d.ts.map +1 -1
- package/dist/src/services/telegram.js +2 -2
- package/dist/src/services/telegram.js.map +1 -1
- package/dist/src/sharedOperations.d.ts +44 -0
- package/dist/src/sharedOperations.d.ts.map +1 -0
- package/dist/src/sharedOperations.js +131 -0
- package/dist/src/sharedOperations.js.map +1 -0
- package/dist/src/version.d.ts +2 -0
- package/dist/src/version.d.ts.map +1 -0
- package/dist/src/version.js +4 -0
- package/dist/src/version.js.map +1 -0
- package/dist/tests/apiCredentialStore.test.js +2 -2
- package/dist/tests/apiCredentialStore.test.js.map +1 -1
- package/dist/tests/apiCredentials.test.js +37 -36
- package/dist/tests/apiCredentials.test.js.map +1 -1
- package/dist/tests/cli.test.js +240 -55
- package/dist/tests/cli.test.js.map +1 -1
- package/dist/tests/config.test.d.ts +2 -0
- package/dist/tests/config.test.d.ts.map +1 -0
- package/dist/tests/config.test.js +150 -0
- package/dist/tests/config.test.js.map +1 -0
- package/dist/tests/gateway.test.d.ts +2 -0
- package/dist/tests/gateway.test.d.ts.map +1 -0
- package/dist/tests/gateway.test.js +566 -0
- package/dist/tests/gateway.test.js.map +1 -0
- package/dist/tests/gatewayClient.test.d.ts +2 -0
- package/dist/tests/gatewayClient.test.d.ts.map +1 -0
- package/dist/tests/gatewayClient.test.js +85 -0
- package/dist/tests/gatewayClient.test.js.map +1 -0
- package/dist/tests/latchkeyEndpoint.test.d.ts +2 -0
- package/dist/tests/latchkeyEndpoint.test.d.ts.map +1 -0
- package/dist/tests/latchkeyEndpoint.test.js +385 -0
- package/dist/tests/latchkeyEndpoint.test.js.map +1 -0
- package/dist/tests/permissions.test.js +15 -0
- package/dist/tests/permissions.test.js.map +1 -1
- package/dist/tests/serviceRegistry.test.d.ts +2 -0
- package/dist/tests/serviceRegistry.test.d.ts.map +1 -0
- package/dist/tests/{registry.test.js → serviceRegistry.test.js} +17 -17
- package/dist/tests/serviceRegistry.test.js.map +1 -0
- package/dist/tests/servicesAgainstRecordings.test.js +3 -3
- package/dist/tests/servicesAgainstRecordings.test.js.map +1 -1
- package/dist/tests/sharedOperations.test.d.ts +2 -0
- package/dist/tests/sharedOperations.test.d.ts.map +1 -0
- package/dist/tests/sharedOperations.test.js +264 -0
- package/dist/tests/sharedOperations.test.js.map +1 -0
- package/package.json +8 -2
- package/dist/src/apiCredentialStore.d.ts.map +0 -1
- package/dist/src/apiCredentialStore.js.map +0 -1
- package/dist/src/apiCredentials.d.ts.map +0 -1
- package/dist/src/apiCredentials.js.map +0 -1
- package/dist/src/apiCredentialsSerialization.d.ts.map +0 -1
- package/dist/src/apiCredentialsSerialization.js.map +0 -1
- package/dist/src/registry.d.ts.map +0 -1
- package/dist/src/registry.js.map +0 -1
- package/dist/tests/registry.test.d.ts +0 -2
- package/dist/tests/registry.test.d.ts.map +0 -1
- package/dist/tests/registry.test.js.map +0 -1
package/dist/tests/cli.test.js
CHANGED
|
@@ -5,12 +5,12 @@ import { tmpdir } from 'node:os';
|
|
|
5
5
|
import { execSync } from 'node:child_process';
|
|
6
6
|
import { Command } from 'commander';
|
|
7
7
|
import { registerCommands } from '../src/cliCommands.js';
|
|
8
|
-
import { extractUrlFromCurlArguments } from '../src/curl.js';
|
|
8
|
+
import { CurlParseError, extractUrlFromCurlArguments } from '../src/curl.js';
|
|
9
9
|
import { hasGraphicalEnvironment } from '../src/playwrightUtils.js';
|
|
10
10
|
import { EncryptedStorage } from '../src/encryptedStorage.js';
|
|
11
11
|
import { Config } from '../src/config.js';
|
|
12
|
-
import {
|
|
13
|
-
import { ApiCredentialStatus } from '../src/apiCredentials.js';
|
|
12
|
+
import { ServiceRegistry } from '../src/serviceRegistry.js';
|
|
13
|
+
import { ApiCredentialStatus } from '../src/apiCredentials/base.js';
|
|
14
14
|
import { SlackApiCredentials } from '../src/services/slack.js';
|
|
15
15
|
import { NoCurlCredentialsNotSupportedError, Service } from '../src/services/core/base.js';
|
|
16
16
|
import { RegisteredService } from '../src/services/core/registered.js';
|
|
@@ -18,7 +18,7 @@ import { GITLAB } from '../src/services/gitlab.js';
|
|
|
18
18
|
import { GITHUB } from '../src/services/github.js';
|
|
19
19
|
import { TELEGRAM } from '../src/services/telegram.js';
|
|
20
20
|
import { deleteRegisteredService, loadRegisteredServices, saveRegisteredService, } from '../src/configDataStore.js';
|
|
21
|
-
import {
|
|
21
|
+
import { loadRegisteredServicesIntoServiceRegistry } from '../src/serviceRegistry.js';
|
|
22
22
|
// Use a fixed test key for deterministic test behavior (32 bytes = 256 bits, base64 encoded)
|
|
23
23
|
const TEST_ENCRYPTION_KEY = 'dGVzdGtleXRlc3RrZXl0ZXN0a2V5dGVzdGtleXRlc3Q=';
|
|
24
24
|
async function writeSecureFile(path, content) {
|
|
@@ -98,13 +98,13 @@ describe('extractUrlFromCurlArguments', () => {
|
|
|
98
98
|
const arguments_ = ['--header', 'Authorization: Bearer token', 'https://api.example.com'];
|
|
99
99
|
expect(extractUrlFromCurlArguments(arguments_)).toBe('https://api.example.com');
|
|
100
100
|
});
|
|
101
|
-
it('should
|
|
101
|
+
it('should throw CurlParseError when no URL is present', () => {
|
|
102
102
|
const arguments_ = ['-X', 'POST', '-H', 'Content-Type: application/json'];
|
|
103
|
-
expect(extractUrlFromCurlArguments(arguments_)).
|
|
103
|
+
expect(() => extractUrlFromCurlArguments(arguments_)).toThrow(CurlParseError);
|
|
104
104
|
});
|
|
105
|
-
it('should
|
|
105
|
+
it('should throw CurlParseError for empty arguments', () => {
|
|
106
106
|
const arguments_ = [];
|
|
107
|
-
expect(extractUrlFromCurlArguments(arguments_)).
|
|
107
|
+
expect(() => extractUrlFromCurlArguments(arguments_)).toThrow(CurlParseError);
|
|
108
108
|
});
|
|
109
109
|
it('should handle verbose flag', () => {
|
|
110
110
|
let arguments_ = ['-v', 'https://api.example.com'];
|
|
@@ -118,6 +118,16 @@ describe('extractUrlFromCurlArguments', () => {
|
|
|
118
118
|
const arguments_ = ['-k', '--compressed', '-s', '-i', 'https://api.example.com'];
|
|
119
119
|
expect(extractUrlFromCurlArguments(arguments_)).toBe('https://api.example.com');
|
|
120
120
|
});
|
|
121
|
+
it('should return the raw arg for schemeless URLs (curl defaults to http://)', () => {
|
|
122
|
+
expect(extractUrlFromCurlArguments(['www.seznam.cz'])).toBe('www.seznam.cz');
|
|
123
|
+
expect(extractUrlFromCurlArguments(['-X', 'POST', 'api.example.com/path'])).toBe('api.example.com/path');
|
|
124
|
+
});
|
|
125
|
+
it('should return null for non-http(s) schemes', () => {
|
|
126
|
+
expect(extractUrlFromCurlArguments(['ftp://example.com/'])).toBeNull();
|
|
127
|
+
});
|
|
128
|
+
it('should propagate CurlParseError for malformed arguments', () => {
|
|
129
|
+
expect(() => extractUrlFromCurlArguments(['-H', 'no-colon-here'])).toThrow(CurlParseError);
|
|
130
|
+
});
|
|
121
131
|
});
|
|
122
132
|
describe('hasGraphicalEnvironment', () => {
|
|
123
133
|
const originalPlatform = process.platform;
|
|
@@ -258,6 +268,10 @@ describe('CLI commands with dependency injection', () => {
|
|
|
258
268
|
browserDisabled: overrides.browserDisabled ?? false,
|
|
259
269
|
countingDisabled: overrides.countingDisabled ?? false,
|
|
260
270
|
permissionsDoNotUseBuiltinSchemas: overrides.permissionsDoNotUseBuiltinSchemas ?? false,
|
|
271
|
+
passthroughUnknown: overrides.passthroughUnknown ?? false,
|
|
272
|
+
gatewayUrl: overrides.gatewayUrl ?? null,
|
|
273
|
+
gatewayListenHost: overrides.gatewayListenHost ?? 'localhost',
|
|
274
|
+
gatewayListenPort: overrides.gatewayListenPort ?? 1989,
|
|
261
275
|
checkSensitiveFilePermissions: () => undefined,
|
|
262
276
|
checkSystemPrerequisites: () => undefined,
|
|
263
277
|
};
|
|
@@ -270,7 +284,7 @@ describe('CLI commands with dependency injection', () => {
|
|
|
270
284
|
loginUrl: 'https://slack.com/signin',
|
|
271
285
|
info: 'Test info for Slack service.',
|
|
272
286
|
credentialCheckCurlArguments: ['https://slack.com/api/auth.test'],
|
|
273
|
-
checkApiCredentials: vi.fn().
|
|
287
|
+
checkApiCredentials: vi.fn().mockResolvedValue(ApiCredentialStatus.Valid),
|
|
274
288
|
setCredentialsExample(serviceName) {
|
|
275
289
|
return `latchkey auth set ${serviceName} -H "Authorization: Bearer xoxb-your-token"`;
|
|
276
290
|
},
|
|
@@ -281,7 +295,7 @@ describe('CLI commands with dependency injection', () => {
|
|
|
281
295
|
login: vi.fn().mockResolvedValue(new SlackApiCredentials('xoxc-test-token', 'test-cookie')),
|
|
282
296
|
}),
|
|
283
297
|
};
|
|
284
|
-
const mockRegistry = new
|
|
298
|
+
const mockRegistry = new ServiceRegistry([mockSlackService]);
|
|
285
299
|
return {
|
|
286
300
|
registry: mockRegistry,
|
|
287
301
|
config: createMockConfig(),
|
|
@@ -289,6 +303,7 @@ describe('CLI commands with dependency injection', () => {
|
|
|
289
303
|
capturedArgs.push(...args);
|
|
290
304
|
return { returncode: 0, stdout: '', stderr: '' };
|
|
291
305
|
},
|
|
306
|
+
runCurlAsync: () => Promise.resolve({ returncode: 0, stdout: Buffer.from(''), stderr: '' }),
|
|
292
307
|
checkPermission: () => Promise.resolve(true),
|
|
293
308
|
confirm: () => Promise.resolve(true),
|
|
294
309
|
exit: (code) => {
|
|
@@ -301,6 +316,7 @@ describe('CLI commands with dependency injection', () => {
|
|
|
301
316
|
errorLog: (message) => {
|
|
302
317
|
errorLogs.push(message);
|
|
303
318
|
},
|
|
319
|
+
version: '0.0.0-test',
|
|
304
320
|
...overrides,
|
|
305
321
|
};
|
|
306
322
|
}
|
|
@@ -400,7 +416,7 @@ describe('CLI commands with dependency injection', () => {
|
|
|
400
416
|
loginUrl: 'https://nologin.example.com',
|
|
401
417
|
info: 'A service without browser login support.',
|
|
402
418
|
credentialCheckCurlArguments: [],
|
|
403
|
-
checkApiCredentials: vi.fn().
|
|
419
|
+
checkApiCredentials: vi.fn().mockResolvedValue(ApiCredentialStatus.Missing),
|
|
404
420
|
setCredentialsExample(serviceName) {
|
|
405
421
|
return `latchkey auth set ${serviceName} -H "Authorization: Bearer <token>"`;
|
|
406
422
|
},
|
|
@@ -409,7 +425,7 @@ describe('CLI commands with dependency injection', () => {
|
|
|
409
425
|
},
|
|
410
426
|
};
|
|
411
427
|
const deps = createMockDependencies({
|
|
412
|
-
registry: new
|
|
428
|
+
registry: new ServiceRegistry([noLoginService]),
|
|
413
429
|
});
|
|
414
430
|
await runCommand(['services', 'list', '--viable'], deps);
|
|
415
431
|
expect(logs).toHaveLength(1);
|
|
@@ -564,7 +580,7 @@ describe('CLI commands with dependency injection', () => {
|
|
|
564
580
|
loginUrl: 'https://nologin.example.com',
|
|
565
581
|
info: 'A service without browser login support.',
|
|
566
582
|
credentialCheckCurlArguments: [],
|
|
567
|
-
checkApiCredentials: vi.fn().
|
|
583
|
+
checkApiCredentials: vi.fn().mockResolvedValue(ApiCredentialStatus.Missing),
|
|
568
584
|
setCredentialsExample(serviceName) {
|
|
569
585
|
return `latchkey auth set ${serviceName} -H "Authorization: Bearer <token>"`;
|
|
570
586
|
},
|
|
@@ -573,7 +589,7 @@ describe('CLI commands with dependency injection', () => {
|
|
|
573
589
|
},
|
|
574
590
|
};
|
|
575
591
|
const deps = createMockDependencies({
|
|
576
|
-
registry: new
|
|
592
|
+
registry: new ServiceRegistry([noLoginService]),
|
|
577
593
|
});
|
|
578
594
|
await runCommand(['services', 'info', 'nologin'], deps);
|
|
579
595
|
const info = JSON.parse(logs[0] ?? '');
|
|
@@ -744,7 +760,7 @@ describe('CLI commands with dependency injection', () => {
|
|
|
744
760
|
const storePath = join(tempDir, 'credentials.json');
|
|
745
761
|
await writeSecureFile(storePath, '{}');
|
|
746
762
|
const deps = createMockDependencies({
|
|
747
|
-
registry: new
|
|
763
|
+
registry: new ServiceRegistry([TELEGRAM]),
|
|
748
764
|
});
|
|
749
765
|
await runCommand(['auth', 'set-nocurl', 'telegram', '123456:ABC-DEF'], deps);
|
|
750
766
|
expect(logs).toContain('Credentials stored.');
|
|
@@ -766,14 +782,14 @@ describe('CLI commands with dependency injection', () => {
|
|
|
766
782
|
});
|
|
767
783
|
it('should return error when telegram token is missing', async () => {
|
|
768
784
|
const deps = createMockDependencies({
|
|
769
|
-
registry: new
|
|
785
|
+
registry: new ServiceRegistry([TELEGRAM]),
|
|
770
786
|
});
|
|
771
787
|
await runCommand(['auth', 'set-nocurl', 'telegram'], deps);
|
|
772
788
|
expect(exitCode).toBe(1);
|
|
773
789
|
});
|
|
774
790
|
it('should return error when telegram token format is invalid', async () => {
|
|
775
791
|
const deps = createMockDependencies({
|
|
776
|
-
registry: new
|
|
792
|
+
registry: new ServiceRegistry([TELEGRAM]),
|
|
777
793
|
});
|
|
778
794
|
await runCommand(['auth', 'set-nocurl', 'telegram', 'not-a-valid-token'], deps);
|
|
779
795
|
expect(exitCode).toBe(1);
|
|
@@ -849,6 +865,38 @@ describe('CLI commands with dependency injection', () => {
|
|
|
849
865
|
await runCommand(['curl', 'https://unknown-api.example.com'], deps);
|
|
850
866
|
expect(exitCode).toBe(1);
|
|
851
867
|
});
|
|
868
|
+
it('should pass through unknown service when passthroughUnknown is enabled', async () => {
|
|
869
|
+
const deps = createMockDependencies({
|
|
870
|
+
config: createMockConfig({ passthroughUnknown: true }),
|
|
871
|
+
});
|
|
872
|
+
await runCommand(['curl', 'https://unknown-api.example.com/test'], deps);
|
|
873
|
+
expect(exitCode).toBe(0);
|
|
874
|
+
expect(capturedArgs).toEqual(['https://unknown-api.example.com/test']);
|
|
875
|
+
expect(errorLogs).toHaveLength(0);
|
|
876
|
+
});
|
|
877
|
+
it('should pass through missing credentials when passthroughUnknown is enabled', async () => {
|
|
878
|
+
const storePath = join(tempDir, 'credentials.json');
|
|
879
|
+
await writeSecureFile(storePath, '{}');
|
|
880
|
+
const deps = createMockDependencies({
|
|
881
|
+
config: createMockConfig({ passthroughUnknown: true }),
|
|
882
|
+
});
|
|
883
|
+
await runCommand(['curl', 'https://slack.com/api/test'], deps);
|
|
884
|
+
expect(exitCode).toBe(0);
|
|
885
|
+
expect(capturedArgs).toEqual(['https://slack.com/api/test']);
|
|
886
|
+
expect(errorLogs).toHaveLength(0);
|
|
887
|
+
});
|
|
888
|
+
it('should still inject credentials for known services when passthroughUnknown is enabled', async () => {
|
|
889
|
+
const storePath = join(tempDir, 'credentials.json');
|
|
890
|
+
await writeSecureFile(storePath, JSON.stringify({
|
|
891
|
+
slack: { objectType: 'slack', token: 'stored-token', dCookie: 'stored-cookie' },
|
|
892
|
+
}));
|
|
893
|
+
const deps = createMockDependencies({
|
|
894
|
+
config: createMockConfig({ passthroughUnknown: true }),
|
|
895
|
+
});
|
|
896
|
+
await runCommand(['curl', 'https://slack.com/api/test'], deps);
|
|
897
|
+
expect(exitCode).toBe(0);
|
|
898
|
+
expect(capturedArgs).toContain('Authorization: Bearer stored-token');
|
|
899
|
+
});
|
|
852
900
|
it('should read credentials from store and not call login', async () => {
|
|
853
901
|
const storePath = join(tempDir, 'credentials.json');
|
|
854
902
|
await writeSecureFile(storePath, JSON.stringify({
|
|
@@ -872,7 +920,7 @@ describe('CLI commands with dependency injection', () => {
|
|
|
872
920
|
getSession: vi.fn().mockReturnValue({ login: mockLogin }),
|
|
873
921
|
};
|
|
874
922
|
const deps = createMockDependencies({
|
|
875
|
-
registry: new
|
|
923
|
+
registry: new ServiceRegistry([mockSlackService]),
|
|
876
924
|
});
|
|
877
925
|
await runCommand(['curl', 'https://slack.com/api/test'], deps);
|
|
878
926
|
expect(mockLogin).not.toHaveBeenCalled();
|
|
@@ -891,7 +939,7 @@ describe('CLI commands with dependency injection', () => {
|
|
|
891
939
|
telegram: { objectType: 'telegramBot', token: '123456:ABC-DEF' },
|
|
892
940
|
}));
|
|
893
941
|
const deps = createMockDependencies({
|
|
894
|
-
registry: new
|
|
942
|
+
registry: new ServiceRegistry([TELEGRAM]),
|
|
895
943
|
});
|
|
896
944
|
await runCommand(['curl', 'https://api.telegram.org/getMe'], deps);
|
|
897
945
|
expect(capturedArgs).toEqual(['https://api.telegram.org/bot123456:ABC-DEF/getMe']);
|
|
@@ -909,7 +957,7 @@ describe('CLI commands with dependency injection', () => {
|
|
|
909
957
|
loginUrl: 'https://nologin.example.com',
|
|
910
958
|
info: 'A service without browser login support.',
|
|
911
959
|
credentialCheckCurlArguments: [],
|
|
912
|
-
checkApiCredentials: vi.fn().
|
|
960
|
+
checkApiCredentials: vi.fn().mockResolvedValue(ApiCredentialStatus.Valid),
|
|
913
961
|
setCredentialsExample(serviceName) {
|
|
914
962
|
return `latchkey auth set ${serviceName} -H "Authorization: Bearer <token>"`;
|
|
915
963
|
},
|
|
@@ -919,7 +967,7 @@ describe('CLI commands with dependency injection', () => {
|
|
|
919
967
|
// No getSession - service doesn't support browser login
|
|
920
968
|
};
|
|
921
969
|
const deps = createMockDependencies({
|
|
922
|
-
registry: new
|
|
970
|
+
registry: new ServiceRegistry([noLoginService]),
|
|
923
971
|
});
|
|
924
972
|
await runCommand(['curl', 'https://nologin.example.com/api/test'], deps);
|
|
925
973
|
expect(exitCode).toBe(0);
|
|
@@ -986,7 +1034,7 @@ describe('CLI commands with dependency injection', () => {
|
|
|
986
1034
|
// No getSession - service doesn't support browser login
|
|
987
1035
|
};
|
|
988
1036
|
const deps = createMockDependencies({
|
|
989
|
-
registry: new
|
|
1037
|
+
registry: new ServiceRegistry([noLoginService]),
|
|
990
1038
|
});
|
|
991
1039
|
await runCommand(['auth', 'browser', 'nologin'], deps);
|
|
992
1040
|
expect(exitCode).toBe(1);
|
|
@@ -1043,7 +1091,7 @@ describe('CLI commands with dependency injection', () => {
|
|
|
1043
1091
|
// No getSession - service doesn't support browser login
|
|
1044
1092
|
};
|
|
1045
1093
|
const deps = createMockDependencies({
|
|
1046
|
-
registry: new
|
|
1094
|
+
registry: new ServiceRegistry([nocurlService]),
|
|
1047
1095
|
});
|
|
1048
1096
|
await runCommand(['auth', 'browser', 'nocurl-only'], deps);
|
|
1049
1097
|
expect(exitCode).toBe(1);
|
|
@@ -1052,7 +1100,7 @@ describe('CLI commands with dependency injection', () => {
|
|
|
1052
1100
|
describe('services register command', () => {
|
|
1053
1101
|
it('should register a new service', async () => {
|
|
1054
1102
|
const deps = createMockDependencies({
|
|
1055
|
-
registry: new
|
|
1103
|
+
registry: new ServiceRegistry([GITLAB]),
|
|
1056
1104
|
});
|
|
1057
1105
|
await runCommand([
|
|
1058
1106
|
'services',
|
|
@@ -1072,7 +1120,7 @@ describe('CLI commands with dependency injection', () => {
|
|
|
1072
1120
|
});
|
|
1073
1121
|
it('should persist registration to config.json', async () => {
|
|
1074
1122
|
const deps = createMockDependencies({
|
|
1075
|
-
registry: new
|
|
1123
|
+
registry: new ServiceRegistry([GITLAB]),
|
|
1076
1124
|
});
|
|
1077
1125
|
await runCommand([
|
|
1078
1126
|
'services',
|
|
@@ -1092,7 +1140,7 @@ describe('CLI commands with dependency injection', () => {
|
|
|
1092
1140
|
});
|
|
1093
1141
|
it('should reject unknown service family', async () => {
|
|
1094
1142
|
const deps = createMockDependencies({
|
|
1095
|
-
registry: new
|
|
1143
|
+
registry: new ServiceRegistry([GITLAB]),
|
|
1096
1144
|
});
|
|
1097
1145
|
await runCommand([
|
|
1098
1146
|
'services',
|
|
@@ -1108,7 +1156,7 @@ describe('CLI commands with dependency injection', () => {
|
|
|
1108
1156
|
});
|
|
1109
1157
|
it('should reject duplicate service name', async () => {
|
|
1110
1158
|
const deps = createMockDependencies({
|
|
1111
|
-
registry: new
|
|
1159
|
+
registry: new ServiceRegistry([GITLAB]),
|
|
1112
1160
|
});
|
|
1113
1161
|
await runCommand([
|
|
1114
1162
|
'services',
|
|
@@ -1124,7 +1172,7 @@ describe('CLI commands with dependency injection', () => {
|
|
|
1124
1172
|
});
|
|
1125
1173
|
it('should canonicalize service name to lowercase', async () => {
|
|
1126
1174
|
const deps = createMockDependencies({
|
|
1127
|
-
registry: new
|
|
1175
|
+
registry: new ServiceRegistry([GITLAB]),
|
|
1128
1176
|
});
|
|
1129
1177
|
await runCommand([
|
|
1130
1178
|
'services',
|
|
@@ -1141,7 +1189,7 @@ describe('CLI commands with dependency injection', () => {
|
|
|
1141
1189
|
});
|
|
1142
1190
|
it('should convert spaces to hyphens in service name', async () => {
|
|
1143
1191
|
const deps = createMockDependencies({
|
|
1144
|
-
registry: new
|
|
1192
|
+
registry: new ServiceRegistry([GITLAB]),
|
|
1145
1193
|
});
|
|
1146
1194
|
await runCommand(['services', 'register', 'my api', '--base-api-url', 'https://api.example.com/'], deps);
|
|
1147
1195
|
expect(exitCode).toBeNull();
|
|
@@ -1150,7 +1198,7 @@ describe('CLI commands with dependency injection', () => {
|
|
|
1150
1198
|
});
|
|
1151
1199
|
it('should reject service name with invalid characters', async () => {
|
|
1152
1200
|
const deps = createMockDependencies({
|
|
1153
|
-
registry: new
|
|
1201
|
+
registry: new ServiceRegistry([GITLAB]),
|
|
1154
1202
|
});
|
|
1155
1203
|
await runCommand(['services', 'register', 'my@service!', '--base-api-url', 'https://api.example.com/'], deps);
|
|
1156
1204
|
expect(exitCode).toBe(1);
|
|
@@ -1160,7 +1208,7 @@ describe('CLI commands with dependency injection', () => {
|
|
|
1160
1208
|
const storePath = join(tempDir, 'credentials.json');
|
|
1161
1209
|
await writeSecureFile(storePath, '{}');
|
|
1162
1210
|
const deps = createMockDependencies({
|
|
1163
|
-
registry: new
|
|
1211
|
+
registry: new ServiceRegistry([GITLAB]),
|
|
1164
1212
|
});
|
|
1165
1213
|
await runCommand([
|
|
1166
1214
|
'services',
|
|
@@ -1179,7 +1227,7 @@ describe('CLI commands with dependency injection', () => {
|
|
|
1179
1227
|
});
|
|
1180
1228
|
it('should persist and restore loginUrl', async () => {
|
|
1181
1229
|
const deps = createMockDependencies({
|
|
1182
|
-
registry: new
|
|
1230
|
+
registry: new ServiceRegistry([GITHUB]),
|
|
1183
1231
|
});
|
|
1184
1232
|
await runCommand([
|
|
1185
1233
|
'services',
|
|
@@ -1197,7 +1245,7 @@ describe('CLI commands with dependency injection', () => {
|
|
|
1197
1245
|
});
|
|
1198
1246
|
it('should reject --login-url without --service-family', async () => {
|
|
1199
1247
|
const deps = createMockDependencies({
|
|
1200
|
-
registry: new
|
|
1248
|
+
registry: new ServiceRegistry([]),
|
|
1201
1249
|
});
|
|
1202
1250
|
await runCommand([
|
|
1203
1251
|
'services',
|
|
@@ -1213,7 +1261,7 @@ describe('CLI commands with dependency injection', () => {
|
|
|
1213
1261
|
});
|
|
1214
1262
|
it('should reject --login-url when service family does not support browser login', async () => {
|
|
1215
1263
|
const deps = createMockDependencies({
|
|
1216
|
-
registry: new
|
|
1264
|
+
registry: new ServiceRegistry([GITLAB]),
|
|
1217
1265
|
});
|
|
1218
1266
|
await runCommand([
|
|
1219
1267
|
'services',
|
|
@@ -1231,7 +1279,7 @@ describe('CLI commands with dependency injection', () => {
|
|
|
1231
1279
|
});
|
|
1232
1280
|
it('should require --login-url when service family supports browser login', async () => {
|
|
1233
1281
|
const deps = createMockDependencies({
|
|
1234
|
-
registry: new
|
|
1282
|
+
registry: new ServiceRegistry([GITHUB]),
|
|
1235
1283
|
});
|
|
1236
1284
|
await runCommand([
|
|
1237
1285
|
'services',
|
|
@@ -1247,7 +1295,7 @@ describe('CLI commands with dependency injection', () => {
|
|
|
1247
1295
|
});
|
|
1248
1296
|
it('should make registered service usable with auth set', async () => {
|
|
1249
1297
|
const deps = createMockDependencies({
|
|
1250
|
-
registry: new
|
|
1298
|
+
registry: new ServiceRegistry([GITLAB]),
|
|
1251
1299
|
});
|
|
1252
1300
|
// Register the service
|
|
1253
1301
|
await runCommand([
|
|
@@ -1270,7 +1318,7 @@ describe('CLI commands with dependency injection', () => {
|
|
|
1270
1318
|
});
|
|
1271
1319
|
it('should register a service without --service-family', async () => {
|
|
1272
1320
|
const deps = createMockDependencies({
|
|
1273
|
-
registry: new
|
|
1321
|
+
registry: new ServiceRegistry([GITLAB]),
|
|
1274
1322
|
});
|
|
1275
1323
|
await runCommand(['services', 'register', 'my-api', '--base-api-url', 'https://api.example.com/'], deps);
|
|
1276
1324
|
expect(exitCode).toBeNull();
|
|
@@ -1282,7 +1330,7 @@ describe('CLI commands with dependency injection', () => {
|
|
|
1282
1330
|
});
|
|
1283
1331
|
it('should persist registration without service family to config.json', async () => {
|
|
1284
1332
|
const deps = createMockDependencies({
|
|
1285
|
-
registry: new
|
|
1333
|
+
registry: new ServiceRegistry([GITLAB]),
|
|
1286
1334
|
});
|
|
1287
1335
|
await runCommand(['services', 'register', 'my-api', '--base-api-url', 'https://api.example.com/'], deps);
|
|
1288
1336
|
const configPath = deps.config.configPath;
|
|
@@ -1295,7 +1343,7 @@ describe('CLI commands with dependency injection', () => {
|
|
|
1295
1343
|
const storePath = join(tempDir, 'credentials.json');
|
|
1296
1344
|
await writeSecureFile(storePath, '{}');
|
|
1297
1345
|
const deps = createMockDependencies({
|
|
1298
|
-
registry: new
|
|
1346
|
+
registry: new ServiceRegistry([GITLAB]),
|
|
1299
1347
|
});
|
|
1300
1348
|
await runCommand(['services', 'register', 'my-api', '--base-api-url', 'https://api.example.com/'], deps);
|
|
1301
1349
|
logs = [];
|
|
@@ -1306,7 +1354,7 @@ describe('CLI commands with dependency injection', () => {
|
|
|
1306
1354
|
});
|
|
1307
1355
|
it('should make service without family usable with auth set and curl', async () => {
|
|
1308
1356
|
const deps = createMockDependencies({
|
|
1309
|
-
registry: new
|
|
1357
|
+
registry: new ServiceRegistry([GITLAB]),
|
|
1310
1358
|
});
|
|
1311
1359
|
// Register the service without family
|
|
1312
1360
|
await runCommand(['services', 'register', 'my-api', '--base-api-url', 'https://api.example.com/'], deps);
|
|
@@ -1328,7 +1376,7 @@ describe('CLI commands with dependency injection', () => {
|
|
|
1328
1376
|
});
|
|
1329
1377
|
it('should reject browser login for service without family', async () => {
|
|
1330
1378
|
const deps = createMockDependencies({
|
|
1331
|
-
registry: new
|
|
1379
|
+
registry: new ServiceRegistry([GITLAB]),
|
|
1332
1380
|
});
|
|
1333
1381
|
await runCommand(['services', 'register', 'my-api', '--base-api-url', 'https://api.example.com/'], deps);
|
|
1334
1382
|
logs = [];
|
|
@@ -1340,7 +1388,7 @@ describe('CLI commands with dependency injection', () => {
|
|
|
1340
1388
|
});
|
|
1341
1389
|
it('should reject set-nocurl for service without family', async () => {
|
|
1342
1390
|
const deps = createMockDependencies({
|
|
1343
|
-
registry: new
|
|
1391
|
+
registry: new ServiceRegistry([GITLAB]),
|
|
1344
1392
|
});
|
|
1345
1393
|
await runCommand(['services', 'register', 'my-api', '--base-api-url', 'https://api.example.com/'], deps);
|
|
1346
1394
|
logs = [];
|
|
@@ -1352,7 +1400,7 @@ describe('CLI commands with dependency injection', () => {
|
|
|
1352
1400
|
});
|
|
1353
1401
|
it('should make registered service usable with curl', async () => {
|
|
1354
1402
|
const deps = createMockDependencies({
|
|
1355
|
-
registry: new
|
|
1403
|
+
registry: new ServiceRegistry([GITLAB]),
|
|
1356
1404
|
});
|
|
1357
1405
|
// Register the service
|
|
1358
1406
|
await runCommand([
|
|
@@ -1384,7 +1432,7 @@ describe('CLI commands with dependency injection', () => {
|
|
|
1384
1432
|
describe('services deregister command', () => {
|
|
1385
1433
|
it('should deregister a registered service', async () => {
|
|
1386
1434
|
const deps = createMockDependencies({
|
|
1387
|
-
registry: new
|
|
1435
|
+
registry: new ServiceRegistry([GITLAB]),
|
|
1388
1436
|
});
|
|
1389
1437
|
// Register a service first
|
|
1390
1438
|
await runCommand([
|
|
@@ -1404,7 +1452,7 @@ describe('CLI commands with dependency injection', () => {
|
|
|
1404
1452
|
});
|
|
1405
1453
|
it('should remove service from config.json', async () => {
|
|
1406
1454
|
const deps = createMockDependencies({
|
|
1407
|
-
registry: new
|
|
1455
|
+
registry: new ServiceRegistry([GITLAB]),
|
|
1408
1456
|
});
|
|
1409
1457
|
await runCommand([
|
|
1410
1458
|
'services',
|
|
@@ -1435,7 +1483,7 @@ describe('CLI commands with dependency injection', () => {
|
|
|
1435
1483
|
});
|
|
1436
1484
|
it('should reject deregistering when credentials still exist', async () => {
|
|
1437
1485
|
const deps = createMockDependencies({
|
|
1438
|
-
registry: new
|
|
1486
|
+
registry: new ServiceRegistry([GITLAB]),
|
|
1439
1487
|
});
|
|
1440
1488
|
// Register a service
|
|
1441
1489
|
await runCommand([
|
|
@@ -1468,7 +1516,7 @@ describe('CLI commands with dependency injection', () => {
|
|
|
1468
1516
|
});
|
|
1469
1517
|
it('should allow deregistering after credentials are cleared', async () => {
|
|
1470
1518
|
const deps = createMockDependencies({
|
|
1471
|
-
registry: new
|
|
1519
|
+
registry: new ServiceRegistry([GITLAB]),
|
|
1472
1520
|
});
|
|
1473
1521
|
// Register
|
|
1474
1522
|
await runCommand([
|
|
@@ -1501,6 +1549,143 @@ describe('CLI commands with dependency injection', () => {
|
|
|
1501
1549
|
expect(logs).toContain("Service 'my-gitlab' deregistered.");
|
|
1502
1550
|
});
|
|
1503
1551
|
});
|
|
1552
|
+
describe('gateway mode (LATCHKEY_GATEWAY)', () => {
|
|
1553
|
+
const GATEWAY_URL = 'http://localhost:9000';
|
|
1554
|
+
const originalFetch = globalThis.fetch;
|
|
1555
|
+
afterEach(() => {
|
|
1556
|
+
globalThis.fetch = originalFetch;
|
|
1557
|
+
});
|
|
1558
|
+
function makeFetchMock(response) {
|
|
1559
|
+
const fetchMock = vi.fn().mockResolvedValue(response);
|
|
1560
|
+
globalThis.fetch = fetchMock;
|
|
1561
|
+
return fetchMock;
|
|
1562
|
+
}
|
|
1563
|
+
it('forwards `services list` to the gateway /latchkey endpoint', async () => {
|
|
1564
|
+
const fetchMock = makeFetchMock(new Response(JSON.stringify({ result: ['slack', 'github'] }), { status: 200 }));
|
|
1565
|
+
const deps = createMockDependencies({
|
|
1566
|
+
config: createMockConfig({ gatewayUrl: GATEWAY_URL }),
|
|
1567
|
+
});
|
|
1568
|
+
await runCommand(['services', 'list', '--builtin'], deps);
|
|
1569
|
+
expect(fetchMock).toHaveBeenCalledTimes(1);
|
|
1570
|
+
const [url, init] = fetchMock.mock.calls[0];
|
|
1571
|
+
expect(url).toBe(`${GATEWAY_URL}/latchkey`);
|
|
1572
|
+
expect(init.method).toBe('POST');
|
|
1573
|
+
expect(JSON.parse(init.body)).toEqual({
|
|
1574
|
+
command: 'services list',
|
|
1575
|
+
params: { builtin: true },
|
|
1576
|
+
});
|
|
1577
|
+
expect(logs).toHaveLength(1);
|
|
1578
|
+
expect(JSON.parse(logs[0] ?? '')).toEqual(['slack', 'github']);
|
|
1579
|
+
});
|
|
1580
|
+
it('forwards `services info` to the gateway /latchkey endpoint', async () => {
|
|
1581
|
+
const fetchMock = makeFetchMock(new Response(JSON.stringify({ result: { type: 'built-in' } }), { status: 200 }));
|
|
1582
|
+
const deps = createMockDependencies({
|
|
1583
|
+
config: createMockConfig({ gatewayUrl: GATEWAY_URL }),
|
|
1584
|
+
});
|
|
1585
|
+
await runCommand(['services', 'info', 'slack'], deps);
|
|
1586
|
+
expect(fetchMock).toHaveBeenCalledTimes(1);
|
|
1587
|
+
const [, init] = fetchMock.mock.calls[0];
|
|
1588
|
+
expect(JSON.parse(init.body)).toEqual({
|
|
1589
|
+
command: 'services info',
|
|
1590
|
+
params: { serviceName: 'slack' },
|
|
1591
|
+
});
|
|
1592
|
+
});
|
|
1593
|
+
it('forwards `auth list` to the gateway /latchkey endpoint', async () => {
|
|
1594
|
+
const fetchMock = makeFetchMock(new Response(JSON.stringify({ result: { slack: { credentialType: 'slack' } } }), {
|
|
1595
|
+
status: 200,
|
|
1596
|
+
}));
|
|
1597
|
+
const deps = createMockDependencies({
|
|
1598
|
+
config: createMockConfig({ gatewayUrl: GATEWAY_URL }),
|
|
1599
|
+
});
|
|
1600
|
+
await runCommand(['auth', 'list'], deps);
|
|
1601
|
+
const [, init] = fetchMock.mock.calls[0];
|
|
1602
|
+
expect(JSON.parse(init.body)).toEqual({ command: 'auth list' });
|
|
1603
|
+
expect(JSON.parse(logs[0] ?? '')).toEqual({
|
|
1604
|
+
slack: { credentialType: 'slack' },
|
|
1605
|
+
});
|
|
1606
|
+
});
|
|
1607
|
+
it('forwards `auth browser` to the gateway /latchkey endpoint', async () => {
|
|
1608
|
+
const fetchMock = makeFetchMock(new Response(JSON.stringify({ result: null }), { status: 200 }));
|
|
1609
|
+
const deps = createMockDependencies({
|
|
1610
|
+
config: createMockConfig({ gatewayUrl: GATEWAY_URL }),
|
|
1611
|
+
});
|
|
1612
|
+
await runCommand(['auth', 'browser', 'slack'], deps);
|
|
1613
|
+
const [, init] = fetchMock.mock.calls[0];
|
|
1614
|
+
expect(JSON.parse(init.body)).toEqual({
|
|
1615
|
+
command: 'auth browser',
|
|
1616
|
+
params: { serviceName: 'slack' },
|
|
1617
|
+
});
|
|
1618
|
+
expect(logs).toContain('Done');
|
|
1619
|
+
});
|
|
1620
|
+
it('forwards `auth browser-prepare` and reports `Already prepared.` when the gateway says so', async () => {
|
|
1621
|
+
makeFetchMock(new Response(JSON.stringify({ result: { alreadyPrepared: true } }), { status: 200 }));
|
|
1622
|
+
const deps = createMockDependencies({
|
|
1623
|
+
config: createMockConfig({ gatewayUrl: GATEWAY_URL }),
|
|
1624
|
+
});
|
|
1625
|
+
await runCommand(['auth', 'browser-prepare', 'slack'], deps);
|
|
1626
|
+
expect(logs).toContain('Already prepared.');
|
|
1627
|
+
});
|
|
1628
|
+
it.each([
|
|
1629
|
+
['services list', ['services', 'list']],
|
|
1630
|
+
['services info', ['services', 'info', 'foo']],
|
|
1631
|
+
['auth list', ['auth', 'list']],
|
|
1632
|
+
['auth browser', ['auth', 'browser', 'slack']],
|
|
1633
|
+
['auth browser-prepare', ['auth', 'browser-prepare', 'slack']],
|
|
1634
|
+
])('reports gateway errors on stderr (not stdout) for `%s`', async (_name, argv) => {
|
|
1635
|
+
makeFetchMock(new Response(JSON.stringify({ error: 'Unknown service: foo.' }), { status: 400 }));
|
|
1636
|
+
const deps = createMockDependencies({
|
|
1637
|
+
config: createMockConfig({ gatewayUrl: GATEWAY_URL }),
|
|
1638
|
+
});
|
|
1639
|
+
await runCommand(argv, deps);
|
|
1640
|
+
expect(exitCode).toBe(1);
|
|
1641
|
+
expect(errorLogs.some((message) => message.includes('Unknown service: foo.'))).toBe(true);
|
|
1642
|
+
// The error message must never be printed to stdout — that would
|
|
1643
|
+
// interleave into JSON output consumed by callers.
|
|
1644
|
+
expect(logs.join('\n')).not.toContain('Unknown service: foo.');
|
|
1645
|
+
expect(logs).toEqual([]);
|
|
1646
|
+
});
|
|
1647
|
+
it('rewrites the curl target URL to the gateway /gateway endpoint', async () => {
|
|
1648
|
+
const fetchMock = vi.fn();
|
|
1649
|
+
globalThis.fetch = fetchMock;
|
|
1650
|
+
const deps = createMockDependencies({
|
|
1651
|
+
config: createMockConfig({ gatewayUrl: GATEWAY_URL }),
|
|
1652
|
+
});
|
|
1653
|
+
await runCommand(['curl', '-X', 'GET', 'https://slack.com/api/auth.test'], deps);
|
|
1654
|
+
expect(fetchMock).not.toHaveBeenCalled();
|
|
1655
|
+
expect(capturedArgs).toEqual([
|
|
1656
|
+
'-X',
|
|
1657
|
+
'GET',
|
|
1658
|
+
`${GATEWAY_URL}/gateway/https://slack.com/api/auth.test`,
|
|
1659
|
+
]);
|
|
1660
|
+
});
|
|
1661
|
+
it('errors when `latchkey curl` has no URL to rewrite', async () => {
|
|
1662
|
+
const deps = createMockDependencies({
|
|
1663
|
+
config: createMockConfig({ gatewayUrl: GATEWAY_URL }),
|
|
1664
|
+
});
|
|
1665
|
+
await runCommand(['curl', '-X', 'GET'], deps);
|
|
1666
|
+
expect(exitCode).toBe(1);
|
|
1667
|
+
expect(capturedArgs).toEqual([]);
|
|
1668
|
+
});
|
|
1669
|
+
it.each([
|
|
1670
|
+
[
|
|
1671
|
+
'services register',
|
|
1672
|
+
['services', 'register', 'my-gitlab', '--base-api-url', 'https://gitlab.example.com'],
|
|
1673
|
+
],
|
|
1674
|
+
['services deregister', ['services', 'deregister', 'my-gitlab']],
|
|
1675
|
+
['auth clear', ['auth', 'clear', 'slack']],
|
|
1676
|
+
['auth set', ['auth', 'set', 'slack', '-H', 'Authorization: Bearer xoxb-test']],
|
|
1677
|
+
['auth set-nocurl', ['auth', 'set-nocurl', 'slack', 'x']],
|
|
1678
|
+
['gateway', ['gateway']],
|
|
1679
|
+
['ensure-browser', ['ensure-browser']],
|
|
1680
|
+
])('refuses to run `%s` in gateway mode', async (_name, argv) => {
|
|
1681
|
+
const deps = createMockDependencies({
|
|
1682
|
+
config: createMockConfig({ gatewayUrl: GATEWAY_URL }),
|
|
1683
|
+
});
|
|
1684
|
+
await runCommand(argv, deps);
|
|
1685
|
+
expect(exitCode).toBe(1);
|
|
1686
|
+
expect(errorLogs.some((message) => message.includes('LATCHKEY_GATEWAY'))).toBe(true);
|
|
1687
|
+
});
|
|
1688
|
+
});
|
|
1504
1689
|
});
|
|
1505
1690
|
describe('registeredServiceStore', () => {
|
|
1506
1691
|
let tempDir;
|
|
@@ -1545,8 +1730,8 @@ describe('registeredServiceStore', () => {
|
|
|
1545
1730
|
baseApiUrl: 'https://gitlab.mycompany.com/api/',
|
|
1546
1731
|
serviceFamily: 'gitlab',
|
|
1547
1732
|
});
|
|
1548
|
-
const registry = new
|
|
1549
|
-
|
|
1733
|
+
const registry = new ServiceRegistry([GITLAB]);
|
|
1734
|
+
loadRegisteredServicesIntoServiceRegistry(configPath, registry);
|
|
1550
1735
|
const service = registry.getByName('my-gitlab');
|
|
1551
1736
|
expect(service).not.toBeNull();
|
|
1552
1737
|
expect(service.baseApiUrls).toEqual(['https://gitlab.mycompany.com/api/']);
|
|
@@ -1558,8 +1743,8 @@ describe('registeredServiceStore', () => {
|
|
|
1558
1743
|
serviceFamily: 'gitlab',
|
|
1559
1744
|
loginUrl: 'https://gitlab.mycompany.com/users/sign_in',
|
|
1560
1745
|
});
|
|
1561
|
-
const registry = new
|
|
1562
|
-
|
|
1746
|
+
const registry = new ServiceRegistry([GITLAB]);
|
|
1747
|
+
loadRegisteredServicesIntoServiceRegistry(configPath, registry);
|
|
1563
1748
|
const service = registry.getByName('my-gitlab');
|
|
1564
1749
|
expect(service).not.toBeNull();
|
|
1565
1750
|
expect(service.loginUrl).toBe('https://gitlab.mycompany.com/users/sign_in');
|
|
@@ -1569,8 +1754,8 @@ describe('registeredServiceStore', () => {
|
|
|
1569
1754
|
saveRegisteredService(configPath, 'my-api', {
|
|
1570
1755
|
baseApiUrl: 'https://api.example.com/',
|
|
1571
1756
|
});
|
|
1572
|
-
const registry = new
|
|
1573
|
-
|
|
1757
|
+
const registry = new ServiceRegistry([GITLAB]);
|
|
1758
|
+
loadRegisteredServicesIntoServiceRegistry(configPath, registry);
|
|
1574
1759
|
const service = registry.getByName('my-api');
|
|
1575
1760
|
expect(service).not.toBeNull();
|
|
1576
1761
|
expect(service.baseApiUrls).toEqual(['https://api.example.com/']);
|
|
@@ -1583,8 +1768,8 @@ describe('registeredServiceStore', () => {
|
|
|
1583
1768
|
baseApiUrl: 'https://unknown.example.com/api/',
|
|
1584
1769
|
serviceFamily: 'nonexistent',
|
|
1585
1770
|
});
|
|
1586
|
-
const registry = new
|
|
1587
|
-
|
|
1771
|
+
const registry = new ServiceRegistry([GITLAB]);
|
|
1772
|
+
loadRegisteredServicesIntoServiceRegistry(configPath, registry);
|
|
1588
1773
|
expect(registry.getByName('my-unknown')).toBeNull();
|
|
1589
1774
|
});
|
|
1590
1775
|
it('should delete a registered service from config', () => {
|