|
1 | 1 | import type { AgentCoreProjectSpec, DeployedResourceState } from '../../../../schema/index.js'; |
2 | 2 | import { computeResourceStatuses, handleProjectStatus } from '../action.js'; |
3 | 3 | import type { StatusContext } from '../action.js'; |
| 4 | +import { buildRuntimeInvocationUrl } from '../constants.js'; |
4 | 5 | import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; |
5 | 6 |
|
6 | 7 | const mockGetAgentRuntimeStatus = vi.fn(); |
@@ -631,3 +632,176 @@ describe('handleProjectStatus — live enrichment', () => { |
631 | 632 | expect(mockGetEvaluator).not.toHaveBeenCalled(); |
632 | 633 | }); |
633 | 634 | }); |
| 635 | + |
| 636 | +describe('buildRuntimeInvocationUrl', () => { |
| 637 | + it('constructs the correct invocation URL with encoded ARN', () => { |
| 638 | + const url = buildRuntimeInvocationUrl( |
| 639 | + 'us-east-1', |
| 640 | + 'arn:aws:bedrock-agentcore:us-east-1:123456789012:runtime/travelplanner_FlightsMcp-abcdefgh' |
| 641 | + ); |
| 642 | + expect(url).toBe( |
| 643 | + 'https://bedrock-agentcore.us-east-1.amazonaws.com/runtimes/arn%3Aaws%3Abedrock-agentcore%3Aus-east-1%3A123456789012%3Aruntime%2Ftravelplanner_FlightsMcp-abcdefgh/invocations' |
| 644 | + ); |
| 645 | + }); |
| 646 | + |
| 647 | + it('handles different regions', () => { |
| 648 | + const url = buildRuntimeInvocationUrl( |
| 649 | + 'eu-west-1', |
| 650 | + 'arn:aws:bedrock-agentcore:eu-west-1:111111111111:runtime/my-agent-xyz' |
| 651 | + ); |
| 652 | + expect(url).toBe( |
| 653 | + 'https://bedrock-agentcore.eu-west-1.amazonaws.com/runtimes/arn%3Aaws%3Abedrock-agentcore%3Aeu-west-1%3A111111111111%3Aruntime%2Fmy-agent-xyz/invocations' |
| 654 | + ); |
| 655 | + }); |
| 656 | +}); |
| 657 | + |
| 658 | +describe('handleProjectStatus — invocation URL enrichment', () => { |
| 659 | + beforeEach(() => { |
| 660 | + mockGetAgentRuntimeStatus.mockReset(); |
| 661 | + mockGetEvaluator.mockReset(); |
| 662 | + mockGetOnlineEvaluationConfig.mockReset(); |
| 663 | + }); |
| 664 | + |
| 665 | + afterEach(() => vi.clearAllMocks()); |
| 666 | + |
| 667 | + it('sets invocationUrl on deployed agents after runtime status enrichment', async () => { |
| 668 | + const runtimeArn = 'arn:aws:bedrock-agentcore:us-east-1:123456789012:runtime/proj_MyAgent-abc123'; |
| 669 | + |
| 670 | + mockGetAgentRuntimeStatus.mockResolvedValue({ |
| 671 | + runtimeId: 'proj_MyAgent-abc123', |
| 672 | + status: 'READY', |
| 673 | + }); |
| 674 | + |
| 675 | + const ctx: StatusContext = { |
| 676 | + project: { |
| 677 | + ...baseProject, |
| 678 | + runtimes: [{ name: 'MyAgent' }], |
| 679 | + } as unknown as AgentCoreProjectSpec, |
| 680 | + awsTargets: [{ name: 'dev', region: 'us-east-1', account: '123456789012' }], |
| 681 | + deployedState: { |
| 682 | + targets: { |
| 683 | + dev: { |
| 684 | + resources: { |
| 685 | + runtimes: { |
| 686 | + MyAgent: { |
| 687 | + runtimeId: 'proj_MyAgent-abc123', |
| 688 | + runtimeArn, |
| 689 | + roleArn: 'arn:aws:iam::123456789012:role/test', |
| 690 | + }, |
| 691 | + }, |
| 692 | + }, |
| 693 | + }, |
| 694 | + }, |
| 695 | + }, |
| 696 | + } as unknown as StatusContext; |
| 697 | + |
| 698 | + const result = await handleProjectStatus(ctx); |
| 699 | + |
| 700 | + expect(result.success).toBe(true); |
| 701 | + const agentEntry = result.resources.find(r => r.resourceType === 'agent' && r.name === 'MyAgent'); |
| 702 | + expect(agentEntry).toBeDefined(); |
| 703 | + expect(agentEntry!.invocationUrl).toBe( |
| 704 | + `https://bedrock-agentcore.us-east-1.amazonaws.com/runtimes/${encodeURIComponent(runtimeArn)}/invocations` |
| 705 | + ); |
| 706 | + }); |
| 707 | + |
| 708 | + it('does not set invocationUrl on local-only agents', async () => { |
| 709 | + const ctx: StatusContext = { |
| 710 | + project: { |
| 711 | + ...baseProject, |
| 712 | + runtimes: [{ name: 'LocalAgent' }], |
| 713 | + } as unknown as AgentCoreProjectSpec, |
| 714 | + awsTargets: [{ name: 'dev', region: 'us-east-1', account: '123456789012' }], |
| 715 | + deployedState: { |
| 716 | + targets: { |
| 717 | + dev: { |
| 718 | + resources: {}, |
| 719 | + }, |
| 720 | + }, |
| 721 | + }, |
| 722 | + } as unknown as StatusContext; |
| 723 | + |
| 724 | + const result = await handleProjectStatus(ctx); |
| 725 | + |
| 726 | + expect(result.success).toBe(true); |
| 727 | + const agentEntry = result.resources.find(r => r.resourceType === 'agent' && r.name === 'LocalAgent'); |
| 728 | + expect(agentEntry).toBeDefined(); |
| 729 | + expect(agentEntry!.invocationUrl).toBeUndefined(); |
| 730 | + }); |
| 731 | + |
| 732 | + it('still sets invocationUrl when runtime status fetch fails', async () => { |
| 733 | + const runtimeArn = 'arn:aws:bedrock-agentcore:us-east-1:123456789012:runtime/proj_FailAgent-xyz'; |
| 734 | + mockGetAgentRuntimeStatus.mockRejectedValue(new Error('Timeout')); |
| 735 | + |
| 736 | + const ctx: StatusContext = { |
| 737 | + project: { |
| 738 | + ...baseProject, |
| 739 | + runtimes: [{ name: 'FailAgent' }], |
| 740 | + } as unknown as AgentCoreProjectSpec, |
| 741 | + awsTargets: [{ name: 'dev', region: 'us-east-1', account: '123456789012' }], |
| 742 | + deployedState: { |
| 743 | + targets: { |
| 744 | + dev: { |
| 745 | + resources: { |
| 746 | + runtimes: { |
| 747 | + FailAgent: { |
| 748 | + runtimeId: 'proj_FailAgent-xyz', |
| 749 | + runtimeArn, |
| 750 | + roleArn: 'arn:aws:iam::123456789012:role/test', |
| 751 | + }, |
| 752 | + }, |
| 753 | + }, |
| 754 | + }, |
| 755 | + }, |
| 756 | + }, |
| 757 | + } as unknown as StatusContext; |
| 758 | + |
| 759 | + const result = await handleProjectStatus(ctx); |
| 760 | + |
| 761 | + expect(result.success).toBe(true); |
| 762 | + const agentEntry = result.resources.find(r => r.resourceType === 'agent' && r.name === 'FailAgent'); |
| 763 | + expect(agentEntry).toBeDefined(); |
| 764 | + expect(agentEntry!.error).toBe('Timeout'); |
| 765 | + expect(agentEntry!.invocationUrl).toBe( |
| 766 | + `https://bedrock-agentcore.us-east-1.amazonaws.com/runtimes/${encodeURIComponent(runtimeArn)}/invocations` |
| 767 | + ); |
| 768 | + }); |
| 769 | + |
| 770 | + it('does not set invocationUrl on pending-removal agents', async () => { |
| 771 | + mockGetAgentRuntimeStatus.mockResolvedValue({ |
| 772 | + runtimeId: 'proj_OldAgent-abc', |
| 773 | + status: 'READY', |
| 774 | + }); |
| 775 | + |
| 776 | + const ctx: StatusContext = { |
| 777 | + project: { |
| 778 | + ...baseProject, |
| 779 | + runtimes: [], |
| 780 | + } as unknown as AgentCoreProjectSpec, |
| 781 | + awsTargets: [{ name: 'dev', region: 'us-east-1', account: '123456789012' }], |
| 782 | + deployedState: { |
| 783 | + targets: { |
| 784 | + dev: { |
| 785 | + resources: { |
| 786 | + runtimes: { |
| 787 | + OldAgent: { |
| 788 | + runtimeId: 'proj_OldAgent-abc', |
| 789 | + runtimeArn: 'arn:aws:bedrock-agentcore:us-east-1:123456789012:runtime/proj_OldAgent-abc', |
| 790 | + roleArn: 'arn:aws:iam::123456789012:role/test', |
| 791 | + }, |
| 792 | + }, |
| 793 | + }, |
| 794 | + }, |
| 795 | + }, |
| 796 | + }, |
| 797 | + } as unknown as StatusContext; |
| 798 | + |
| 799 | + const result = await handleProjectStatus(ctx); |
| 800 | + |
| 801 | + expect(result.success).toBe(true); |
| 802 | + const agentEntry = result.resources.find(r => r.resourceType === 'agent' && r.name === 'OldAgent'); |
| 803 | + expect(agentEntry).toBeDefined(); |
| 804 | + expect(agentEntry!.deploymentState).toBe('pending-removal'); |
| 805 | + expect(agentEntry!.invocationUrl).toBeUndefined(); |
| 806 | + }); |
| 807 | +}); |
0 commit comments