Skip to content

Commit 4cac346

Browse files
Merge pull request #5 from jesseturner21/feat/import-entrypoint-flag
feat(import): add runtime/memory import subcommands with entrypoint detection
2 parents 1a45c28 + 51dd78f commit 4cac346

30 files changed

Lines changed: 3243 additions & 276 deletions

src/cli/aws/__tests__/agentcore-control.test.ts

Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
import {
2+
getAgentRuntimeDetail,
23
getAgentRuntimeStatus,
34
getEvaluator,
45
getOnlineEvaluationConfig,
6+
listAllAgentRuntimes,
7+
listAllMemories,
58
listEvaluators,
69
updateOnlineEvalExecutionStatus,
710
} from '../agentcore-control.js';
@@ -24,9 +27,18 @@ vi.mock('@aws-sdk/client-bedrock-agentcore-control', () => ({
2427
GetOnlineEvaluationConfigCommand: class {
2528
constructor(public input: unknown) {}
2629
},
30+
ListAgentRuntimesCommand: class {
31+
constructor(public input: unknown) {}
32+
},
33+
ListMemoriesCommand: class {
34+
constructor(public input: unknown) {}
35+
},
2736
ListEvaluatorsCommand: class {
2837
constructor(public input: unknown) {}
2938
},
39+
ListTagsForResourceCommand: class {
40+
constructor(public input: unknown) {}
41+
},
3042
UpdateOnlineEvaluationConfigCommand: class {
3143
constructor(public input: unknown) {}
3244
},
@@ -305,6 +317,191 @@ describe('getOnlineEvaluationConfig', () => {
305317
});
306318
});
307319

320+
describe('listAllAgentRuntimes', () => {
321+
beforeEach(() => {
322+
vi.clearAllMocks();
323+
});
324+
325+
it('returns all runtimes from a single page', async () => {
326+
mockSend.mockResolvedValue({
327+
agentRuntimes: [
328+
{ agentRuntimeId: 'rt-1', agentRuntimeArn: 'arn-1', agentRuntimeName: 'runtime-1', status: 'READY' },
329+
],
330+
nextToken: undefined,
331+
});
332+
333+
const result = await listAllAgentRuntimes({ region: 'us-east-1' });
334+
expect(result).toHaveLength(1);
335+
expect(result[0]!.agentRuntimeId).toBe('rt-1');
336+
expect(mockSend).toHaveBeenCalledTimes(1);
337+
});
338+
339+
it('paginates across multiple pages', async () => {
340+
mockSend
341+
.mockResolvedValueOnce({
342+
agentRuntimes: [{ agentRuntimeId: 'rt-1', agentRuntimeArn: 'arn-1', agentRuntimeName: 'r1', status: 'READY' }],
343+
nextToken: 'page2',
344+
})
345+
.mockResolvedValueOnce({
346+
agentRuntimes: [{ agentRuntimeId: 'rt-2', agentRuntimeArn: 'arn-2', agentRuntimeName: 'r2', status: 'READY' }],
347+
nextToken: 'page3',
348+
})
349+
.mockResolvedValueOnce({
350+
agentRuntimes: [{ agentRuntimeId: 'rt-3', agentRuntimeArn: 'arn-3', agentRuntimeName: 'r3', status: 'READY' }],
351+
nextToken: undefined,
352+
});
353+
354+
const result = await listAllAgentRuntimes({ region: 'us-east-1' });
355+
expect(result).toHaveLength(3);
356+
expect(result.map(r => r.agentRuntimeId)).toEqual(['rt-1', 'rt-2', 'rt-3']);
357+
expect(mockSend).toHaveBeenCalledTimes(3);
358+
});
359+
360+
it('returns empty array when no runtimes exist', async () => {
361+
mockSend.mockResolvedValue({ agentRuntimes: undefined, nextToken: undefined });
362+
363+
const result = await listAllAgentRuntimes({ region: 'us-east-1' });
364+
expect(result).toEqual([]);
365+
});
366+
});
367+
368+
describe('listAllMemories', () => {
369+
beforeEach(() => {
370+
vi.clearAllMocks();
371+
});
372+
373+
it('returns all memories from a single page', async () => {
374+
mockSend.mockResolvedValue({
375+
memories: [{ id: 'mem-1', arn: 'arn-1', status: 'ACTIVE' }],
376+
nextToken: undefined,
377+
});
378+
379+
const result = await listAllMemories({ region: 'us-east-1' });
380+
expect(result).toHaveLength(1);
381+
expect(result[0]!.memoryId).toBe('mem-1');
382+
expect(mockSend).toHaveBeenCalledTimes(1);
383+
});
384+
385+
it('paginates across multiple pages', async () => {
386+
mockSend
387+
.mockResolvedValueOnce({
388+
memories: [{ id: 'mem-1', arn: 'arn-1', status: 'ACTIVE' }],
389+
nextToken: 'page2',
390+
})
391+
.mockResolvedValueOnce({
392+
memories: [{ id: 'mem-2', arn: 'arn-2', status: 'ACTIVE' }],
393+
nextToken: undefined,
394+
});
395+
396+
const result = await listAllMemories({ region: 'us-east-1' });
397+
expect(result).toHaveLength(2);
398+
expect(result.map(m => m.memoryId)).toEqual(['mem-1', 'mem-2']);
399+
expect(mockSend).toHaveBeenCalledTimes(2);
400+
});
401+
402+
it('returns empty array when no memories exist', async () => {
403+
mockSend.mockResolvedValue({ memories: undefined, nextToken: undefined });
404+
405+
const result = await listAllMemories({ region: 'us-east-1' });
406+
expect(result).toEqual([]);
407+
});
408+
});
409+
410+
describe('getAgentRuntimeDetail — new fields', () => {
411+
beforeEach(() => {
412+
vi.clearAllMocks();
413+
});
414+
415+
const baseResponse = {
416+
agentRuntimeId: 'rt-123',
417+
agentRuntimeArn: 'arn:aws:bedrock-agentcore:us-east-1:123:runtime/rt-123',
418+
agentRuntimeName: 'my-runtime',
419+
status: 'READY',
420+
roleArn: 'arn:aws:iam::123:role/test',
421+
networkConfiguration: { networkMode: 'PUBLIC' },
422+
protocolConfiguration: { serverProtocol: 'HTTP' },
423+
agentRuntimeArtifact: { codeConfiguration: { runtime: 'PYTHON_3_12', entryPoint: ['main.py'] } },
424+
};
425+
426+
it('extracts environmentVariables when present', async () => {
427+
mockSend.mockResolvedValue({
428+
...baseResponse,
429+
environmentVariables: { API_KEY: 'secret', DB_HOST: 'localhost' },
430+
});
431+
432+
const result = await getAgentRuntimeDetail({ region: 'us-east-1', runtimeId: 'rt-123' });
433+
expect(result.environmentVariables).toEqual({ API_KEY: 'secret', DB_HOST: 'localhost' });
434+
});
435+
436+
it('returns undefined environmentVariables when empty', async () => {
437+
mockSend.mockResolvedValue({ ...baseResponse, environmentVariables: {} });
438+
439+
const result = await getAgentRuntimeDetail({ region: 'us-east-1', runtimeId: 'rt-123' });
440+
expect(result.environmentVariables).toBeUndefined();
441+
});
442+
443+
it('extracts lifecycleConfiguration when present', async () => {
444+
mockSend.mockResolvedValue({
445+
...baseResponse,
446+
lifecycleConfiguration: { idleRuntimeSessionTimeout: 600, maxLifetime: 3600 },
447+
});
448+
449+
const result = await getAgentRuntimeDetail({ region: 'us-east-1', runtimeId: 'rt-123' });
450+
expect(result.lifecycleConfiguration).toEqual({ idleRuntimeSessionTimeout: 600, maxLifetime: 3600 });
451+
});
452+
453+
it('returns undefined lifecycleConfiguration when absent', async () => {
454+
mockSend.mockResolvedValue({ ...baseResponse });
455+
456+
const result = await getAgentRuntimeDetail({ region: 'us-east-1', runtimeId: 'rt-123' });
457+
expect(result.lifecycleConfiguration).toBeUndefined();
458+
});
459+
460+
it('extracts requestHeaderAllowlist from requestHeaderConfiguration union', async () => {
461+
mockSend.mockResolvedValue({
462+
...baseResponse,
463+
requestHeaderConfiguration: {
464+
requestHeaderAllowlist: ['X-Custom-Header', 'Authorization'],
465+
},
466+
});
467+
468+
const result = await getAgentRuntimeDetail({ region: 'us-east-1', runtimeId: 'rt-123' });
469+
expect(result.requestHeaderAllowlist).toEqual(['X-Custom-Header', 'Authorization']);
470+
});
471+
472+
it('returns undefined requestHeaderAllowlist when not present', async () => {
473+
mockSend.mockResolvedValue({ ...baseResponse });
474+
475+
const result = await getAgentRuntimeDetail({ region: 'us-east-1', runtimeId: 'rt-123' });
476+
expect(result.requestHeaderAllowlist).toBeUndefined();
477+
});
478+
479+
it('fetches tags via ListTagsForResource', async () => {
480+
// First call: GetAgentRuntime, second call: ListTagsForResource
481+
mockSend
482+
.mockResolvedValueOnce({ ...baseResponse })
483+
.mockResolvedValueOnce({ tags: { env: 'prod', team: 'platform' } });
484+
485+
const result = await getAgentRuntimeDetail({ region: 'us-east-1', runtimeId: 'rt-123' });
486+
expect(result.tags).toEqual({ env: 'prod', team: 'platform' });
487+
expect(mockSend).toHaveBeenCalledTimes(2);
488+
});
489+
490+
it('returns undefined tags when ListTagsForResource returns empty', async () => {
491+
mockSend.mockResolvedValueOnce({ ...baseResponse }).mockResolvedValueOnce({ tags: {} });
492+
493+
const result = await getAgentRuntimeDetail({ region: 'us-east-1', runtimeId: 'rt-123' });
494+
expect(result.tags).toBeUndefined();
495+
});
496+
497+
it('returns undefined tags when ListTagsForResource fails', async () => {
498+
mockSend.mockResolvedValueOnce({ ...baseResponse }).mockRejectedValueOnce(new Error('AccessDenied'));
499+
500+
const result = await getAgentRuntimeDetail({ region: 'us-east-1', runtimeId: 'rt-123' });
501+
expect(result.tags).toBeUndefined();
502+
});
503+
});
504+
308505
describe('listEvaluators', () => {
309506
beforeEach(() => {
310507
vi.clearAllMocks();

0 commit comments

Comments
 (0)