Skip to content

Commit 41365e4

Browse files
authored
test: enable gateway test coverage (#487)
* test: unskip and update gateway tests for existing-endpoint path - Unskip all 4 gateway test files (add/remove gateway/gateway-target) - Update validGatewayTargetOptions fixture to existing-endpoint - Remove create-new path tests (not user-facing yet) - Add tests: rejects create-new source, requires endpoint, invalid OAuth discovery URL, auto-create OAuth credential - Rewrite cascade/removal tests to use --endpoint instead of non-existent 'add bind gateway' and --language/--host flags * test: add gateway integration test and gateway-env unit tests - Add end-to-end gateway lifecycle test (add gateway, add target with Exa MCP endpoint, remove target, remove gateway) - Add unit tests for getGatewayEnvVars (dev environment setup)
1 parent 0d6fc31 commit 41365e4

File tree

7 files changed

+264
-104
lines changed

7 files changed

+264
-104
lines changed
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import { createTestProject, runCLI } from '../src/test-utils/index.js';
2+
import type { TestProject } from '../src/test-utils/index.js';
3+
import { readFile } from 'node:fs/promises';
4+
import { join } from 'node:path';
5+
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
6+
7+
async function readMcpConfig(projectPath: string) {
8+
return JSON.parse(await readFile(join(projectPath, 'agentcore/mcp.json'), 'utf-8'));
9+
}
10+
11+
describe('integration: add and remove gateway with external MCP server', () => {
12+
let project: TestProject;
13+
const gatewayName = 'ExaGateway';
14+
const targetName = 'ExaSearch';
15+
16+
beforeAll(async () => {
17+
project = await createTestProject({ noAgent: true });
18+
});
19+
20+
afterAll(async () => {
21+
await project.cleanup();
22+
});
23+
24+
describe('gateway lifecycle', () => {
25+
it('adds a gateway', async () => {
26+
const result = await runCLI(['add', 'gateway', '--name', gatewayName, '--json'], project.projectPath);
27+
28+
expect(result.exitCode, `stdout: ${result.stdout}, stderr: ${result.stderr}`).toBe(0);
29+
const json = JSON.parse(result.stdout);
30+
expect(json.success).toBe(true);
31+
32+
const mcpSpec = await readMcpConfig(project.projectPath);
33+
const gateway = mcpSpec.agentCoreGateways?.find((g: { name: string }) => g.name === gatewayName);
34+
expect(gateway, `Gateway "${gatewayName}" should be in mcp.json`).toBeTruthy();
35+
expect(gateway.authorizerType).toBe('NONE');
36+
});
37+
38+
it('adds an external MCP server target to the gateway', async () => {
39+
const result = await runCLI(
40+
[
41+
'add',
42+
'gateway-target',
43+
'--name',
44+
targetName,
45+
'--endpoint',
46+
'https://mcp.exa.ai/mcp',
47+
'--gateway',
48+
gatewayName,
49+
'--json',
50+
],
51+
project.projectPath
52+
);
53+
54+
expect(result.exitCode, `stdout: ${result.stdout}, stderr: ${result.stderr}`).toBe(0);
55+
const json = JSON.parse(result.stdout);
56+
expect(json.success).toBe(true);
57+
58+
const mcpSpec = await readMcpConfig(project.projectPath);
59+
const gateway = mcpSpec.agentCoreGateways?.find((g: { name: string }) => g.name === gatewayName);
60+
const target = gateway?.targets?.find((t: { name: string }) => t.name === targetName);
61+
expect(target, `Target "${targetName}" should be in gateway targets`).toBeTruthy();
62+
});
63+
64+
it('removes the gateway target', async () => {
65+
const result = await runCLI(['remove', 'gateway-target', '--name', targetName, '--json'], project.projectPath);
66+
67+
expect(result.exitCode, `stdout: ${result.stdout}, stderr: ${result.stderr}`).toBe(0);
68+
const json = JSON.parse(result.stdout);
69+
expect(json.success).toBe(true);
70+
71+
const mcpSpec = await readMcpConfig(project.projectPath);
72+
const gateway = mcpSpec.agentCoreGateways?.find((g: { name: string }) => g.name === gatewayName);
73+
const targets = gateway?.targets ?? [];
74+
const found = targets.find((t: { name: string }) => t.name === targetName);
75+
expect(found, `Target "${targetName}" should be removed`).toBeFalsy();
76+
});
77+
78+
it('removes the gateway', async () => {
79+
const result = await runCLI(['remove', 'gateway', '--name', gatewayName, '--json'], project.projectPath);
80+
81+
expect(result.exitCode, `stdout: ${result.stdout}, stderr: ${result.stderr}`).toBe(0);
82+
const json = JSON.parse(result.stdout);
83+
expect(json.success).toBe(true);
84+
85+
const mcpSpec = await readMcpConfig(project.projectPath);
86+
const gateways = mcpSpec.agentCoreGateways ?? [];
87+
const found = gateways.find((g: { name: string }) => g.name === gatewayName);
88+
expect(found, `Gateway "${gatewayName}" should be removed`).toBeFalsy();
89+
});
90+
});
91+
});

src/cli/commands/add/__tests__/actions.test.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,20 @@ import { afterEach, describe, expect, it, vi } from 'vitest';
44

55
const mockCreateToolFromWizard = vi.fn().mockResolvedValue({ toolName: 'test', projectPath: '/tmp' });
66
const mockCreateExternalGatewayTarget = vi.fn().mockResolvedValue({ toolName: 'test', projectPath: '' });
7+
const mockCreateCredential = vi.fn().mockResolvedValue(undefined);
78

89
vi.mock('../../../operations/mcp/create-mcp', () => ({
910
createToolFromWizard: (...args: unknown[]) => mockCreateToolFromWizard(...args),
1011
createExternalGatewayTarget: (...args: unknown[]) => mockCreateExternalGatewayTarget(...args),
1112
createGatewayFromWizard: vi.fn(),
1213
}));
1314

15+
vi.mock('../../../operations/identity/create-identity', () => ({
16+
createCredential: (...args: unknown[]) => mockCreateCredential(...args),
17+
computeDefaultCredentialEnvVarName: vi.fn(),
18+
resolveCredentialStrategy: vi.fn(),
19+
}));
20+
1421
describe('buildGatewayTargetConfig', () => {
1522
it('maps name, gateway, language correctly', () => {
1623
const options: ValidatedAddGatewayTargetOptions = {
@@ -97,4 +104,31 @@ describe('handleAddGatewayTarget', () => {
97104
expect(mockCreateExternalGatewayTarget).toHaveBeenCalledOnce();
98105
expect(mockCreateToolFromWizard).not.toHaveBeenCalled();
99106
});
107+
108+
it('auto-creates OAuth credential when inline fields provided', async () => {
109+
const options: ValidatedAddGatewayTargetOptions = {
110+
name: 'my-tool',
111+
language: 'Other',
112+
host: 'Lambda',
113+
source: 'existing-endpoint',
114+
endpoint: 'https://example.com/mcp',
115+
gateway: 'my-gw',
116+
oauthClientId: 'cid',
117+
oauthClientSecret: 'csec',
118+
oauthDiscoveryUrl: 'https://auth.example.com',
119+
oauthScopes: 'read,write',
120+
};
121+
122+
await handleAddGatewayTarget(options);
123+
124+
expect(mockCreateCredential).toHaveBeenCalledWith({
125+
type: 'OAuthCredentialProvider',
126+
name: 'my-tool-oauth',
127+
discoveryUrl: 'https://auth.example.com',
128+
clientId: 'cid',
129+
clientSecret: 'csec',
130+
scopes: ['read', 'write'],
131+
});
132+
expect(options.credentialName).toBe('my-tool-oauth');
133+
});
100134
});

src/cli/commands/add/__tests__/add-gateway-target.test.ts

Lines changed: 19 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,7 @@ import { tmpdir } from 'node:os';
55
import { join } from 'node:path';
66
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
77

8-
// Gateway Target feature is disabled (coming soon) - skip all tests
9-
describe.skip('add gateway-target command', () => {
8+
describe('add gateway-target command', () => {
109
let testDir: string;
1110
let projectDir: string;
1211
const gatewayName = 'test-gateway';
@@ -22,6 +21,12 @@ describe.skip('add gateway-target command', () => {
2221
throw new Error(`Failed to create project: ${result.stdout} ${result.stderr}`);
2322
}
2423
projectDir = join(testDir, projectName);
24+
25+
// Create gateway for tests
26+
const gwResult = await runCLI(['add', 'gateway', '--name', gatewayName, '--json'], projectDir);
27+
if (gwResult.exitCode !== 0) {
28+
throw new Error(`Failed to create gateway: ${gwResult.stdout} ${gwResult.stderr}`);
29+
}
2530
});
2631

2732
afterAll(async () => {
@@ -37,53 +42,31 @@ describe.skip('add gateway-target command', () => {
3742
expect(json.error.includes('--name'), `Error: ${json.error}`).toBeTruthy();
3843
});
3944

40-
it('validates language', async () => {
45+
it('requires endpoint', async () => {
4146
const result = await runCLI(
42-
['add', 'gateway-target', '--name', 'test', '--language', 'InvalidLang', '--json'],
47+
['add', 'gateway-target', '--name', 'noendpoint', '--gateway', gatewayName, '--json'],
4348
projectDir
4449
);
4550
expect(result.exitCode).toBe(1);
4651
const json = JSON.parse(result.stdout);
4752
expect(json.success).toBe(false);
48-
expect(
49-
json.error.toLowerCase().includes('invalid') || json.error.toLowerCase().includes('valid options'),
50-
`Error should mention invalid language: ${json.error}`
51-
).toBeTruthy();
52-
});
53-
54-
it('accepts Other as valid language option', async () => {
55-
const result = await runCLI(
56-
['add', 'gateway-target', '--name', 'container-tool', '--language', 'Other', '--json'],
57-
projectDir
58-
);
59-
60-
// Should fail with "not yet supported" error, not validation error
61-
expect(result.exitCode).toBe(1);
62-
const json = JSON.parse(result.stdout);
63-
expect(json.success).toBe(false);
64-
expect(
65-
json.error.toLowerCase().includes('not yet supported') || json.error.toLowerCase().includes('other'),
66-
`Error should mention Other not supported: ${json.error}`
67-
).toBeTruthy();
53+
expect(json.error.includes('--endpoint'), `Error: ${json.error}`).toBeTruthy();
6854
});
6955
});
7056

71-
// Gateway disabled - skip behind-gateway tests until gateway feature is enabled
72-
describe.skip('behind-gateway', () => {
73-
it('creates behind-gateway tool', async () => {
74-
const toolName = `gwtool${Date.now()}`;
57+
describe('existing-endpoint', () => {
58+
it('adds existing-endpoint target to gateway', async () => {
59+
const targetName = `target${Date.now()}`;
7560
const result = await runCLI(
7661
[
7762
'add',
7863
'gateway-target',
7964
'--name',
80-
toolName,
81-
'--language',
82-
'Python',
65+
targetName,
66+
'--endpoint',
67+
'https://mcp.exa.ai/mcp',
8368
'--gateway',
8469
gatewayName,
85-
'--host',
86-
'Lambda',
8770
'--json',
8871
],
8972
projectDir
@@ -92,59 +75,12 @@ describe.skip('add gateway-target command', () => {
9275
expect(result.exitCode, `stdout: ${result.stdout}, stderr: ${result.stderr}`).toBe(0);
9376
const json = JSON.parse(result.stdout);
9477
expect(json.success).toBe(true);
95-
expect(json.toolName).toBe(toolName);
9678

97-
// Verify in mcp.json gateway targets
79+
// Verify in mcp.json
9880
const mcpSpec = JSON.parse(await readFile(join(projectDir, 'agentcore/mcp.json'), 'utf-8'));
9981
const gateway = mcpSpec.agentCoreGateways.find((g: { name: string }) => g.name === gatewayName);
100-
const target = gateway?.targets?.find((t: { name: string }) => t.name === toolName);
101-
expect(target, 'Tool should be in gateway targets').toBeTruthy();
102-
});
103-
104-
it('requires gateway for behind-gateway', async () => {
105-
const result = await runCLI(
106-
['add', 'gateway-target', '--name', 'no-gw', '--language', 'Python', '--host', 'Lambda', '--json'],
107-
projectDir
108-
);
109-
expect(result.exitCode).toBe(1);
110-
const json = JSON.parse(result.stdout);
111-
expect(json.success).toBe(false);
112-
expect(json.error.includes('--gateway'), `Error: ${json.error}`).toBeTruthy();
113-
});
114-
115-
it('requires host for behind-gateway', async () => {
116-
const result = await runCLI(
117-
['add', 'gateway-target', '--name', 'no-host', '--language', 'Python', '--gateway', gatewayName, '--json'],
118-
projectDir
119-
);
120-
expect(result.exitCode).toBe(1);
121-
const json = JSON.parse(result.stdout);
122-
expect(json.success).toBe(false);
123-
expect(json.error.includes('--host'), `Error: ${json.error}`).toBeTruthy();
124-
});
125-
126-
it('returns clear error for Other language with behind-gateway', async () => {
127-
const result = await runCLI(
128-
[
129-
'add',
130-
'gateway-target',
131-
'--name',
132-
'gateway-container',
133-
'--language',
134-
'Other',
135-
'--gateway',
136-
gatewayName,
137-
'--host',
138-
'Lambda',
139-
'--json',
140-
],
141-
projectDir
142-
);
143-
144-
expect(result.exitCode).toBe(1);
145-
const json = JSON.parse(result.stdout);
146-
expect(json.success).toBe(false);
147-
expect(json.error.length > 0, 'Should have error message').toBeTruthy();
82+
const target = gateway?.targets?.find((t: { name: string }) => t.name === targetName);
83+
expect(target, 'Target should be in gateway targets').toBeTruthy();
14884
});
14985
});
15086
});

src/cli/commands/add/__tests__/add-gateway.test.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,7 @@ import { tmpdir } from 'node:os';
55
import { join } from 'node:path';
66
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
77

8-
// Gateway disabled - skip until gateway feature is enabled
9-
describe.skip('add gateway command', () => {
8+
describe('add gateway command', () => {
109
let testDir: string;
1110
let projectDir: string;
1211

src/cli/commands/remove/__tests__/remove-gateway-target.test.ts

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,7 @@ import { tmpdir } from 'node:os';
55
import { join } from 'node:path';
66
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
77

8-
// Gateway Target feature is disabled (coming soon) - skip all tests
9-
describe.skip('remove gateway-target command', () => {
8+
describe('remove gateway-target command', () => {
109
let testDir: string;
1110
let projectDir: string;
1211

@@ -45,28 +44,25 @@ describe.skip('remove gateway-target command', () => {
4544
});
4645
});
4746

48-
// Gateway disabled - skip behind-gateway tests until gateway feature is enabled
49-
describe.skip('remove behind-gateway tool', () => {
50-
it('removes behind-gateway tool from gateway targets', async () => {
51-
// Create a fresh gateway for this test to avoid conflicts with existing tools
47+
describe('remove existing-endpoint target', () => {
48+
it('removes target from gateway', async () => {
49+
// Create a fresh gateway
5250
const tempGateway = `TempGw${Date.now()}`;
5351
const gwResult = await runCLI(['add', 'gateway', '--name', tempGateway, '--json'], projectDir);
5452
expect(gwResult.exitCode, `gateway add failed: ${gwResult.stdout}`).toBe(0);
5553

56-
// Add a tool to the fresh gateway
54+
// Add a target to the gateway
5755
const tempTool = `tempTool${Date.now()}`;
5856
const addResult = await runCLI(
5957
[
6058
'add',
6159
'gateway-target',
6260
'--name',
6361
tempTool,
64-
'--language',
65-
'Python',
62+
'--endpoint',
63+
'https://example.com/mcp',
6664
'--gateway',
6765
tempGateway,
68-
'--host',
69-
'Lambda',
7066
'--json',
7167
],
7268
projectDir

src/cli/commands/remove/__tests__/remove-gateway.test.ts

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,7 @@ import { tmpdir } from 'node:os';
55
import { join } from 'node:path';
66
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
77

8-
// Gateway disabled - skip until gateway feature is enabled
9-
describe.skip('remove gateway command', () => {
8+
describe('remove gateway command', () => {
109
let testDir: string;
1110
let projectDir: string;
1211
const gatewayName = 'TestGateway';
@@ -93,15 +92,29 @@ describe.skip('remove gateway command', () => {
9392
expect(!gateway, 'Gateway should be removed').toBeTruthy();
9493
});
9594

96-
it('removes gateway with attached agents using cascade policy (default)', async () => {
97-
// Bind gateway to agent
98-
const bindResult = await runCLI(
99-
['add', 'bind', 'gateway', '--agent', agentName, '--gateway', gatewayName, '--name', 'GatewayTool', '--json'],
95+
it('removes gateway with targets attached', async () => {
96+
// Re-add gateway since previous test may have removed it
97+
await runCLI(['add', 'gateway', '--name', gatewayName, '--json'], projectDir);
98+
99+
// Add a target to the gateway
100+
const targetName = `target${Date.now()}`;
101+
const addResult = await runCLI(
102+
[
103+
'add',
104+
'gateway-target',
105+
'--name',
106+
targetName,
107+
'--endpoint',
108+
'https://example.com/mcp',
109+
'--gateway',
110+
gatewayName,
111+
'--json',
112+
],
100113
projectDir
101114
);
102-
expect(bindResult.exitCode, `bind failed: ${bindResult.stdout}`).toBe(0);
115+
expect(addResult.exitCode, `add target failed: ${addResult.stdout}`).toBe(0);
103116

104-
// Remove with cascade policy (default) - should succeed and clean up references
117+
// Remove gateway - should succeed and clean up targets
105118
const result = await runCLI(['remove', 'gateway', '--name', gatewayName, '--json'], projectDir);
106119
expect(result.exitCode, `stdout: ${result.stdout}`).toBe(0);
107120
const json = JSON.parse(result.stdout);

0 commit comments

Comments
 (0)