@@ -14,6 +14,17 @@ const lambdaCompute = {
1414 implementation : { language : "Python" as const , path : "tools" , handler : "handler.main" } ,
1515 pythonVersion : "PYTHON_3_12" as const ,
1616} ;
17+ const githubPassthrough = {
18+ name : "github" ,
19+ targetType : "passthrough" ,
20+ passthrough : { endpoint : "https://api.github.com" , protocolType : "CUSTOM" } ,
21+ } ;
22+ const apiKeyAuth = { type : "API_KEY" , credentialName : "githubToken" } ;
23+ const targetIssues = ( target : object ) =>
24+ AgentCoreGatewayTargetSchema . safeParse ( target ) . error ?. issues . map ( ( { path, message } ) => ( {
25+ path,
26+ message,
27+ } ) ) ;
1728describe ( "gateway custom validation" , ( ) => {
1829 it ( "binds compute runtime versions to implementation languages" , ( ) => {
1930 expect (
@@ -236,6 +247,121 @@ describe("gateway custom validation", () => {
236247 } ) . success ,
237248 ) . toBe ( true ) ;
238249 } ) ;
250+ it . each ( [
251+ [ "passthrough with no API key placement" , githubPassthrough , { } ] ,
252+ [
253+ "passthrough with an Authorization Bearer header" ,
254+ githubPassthrough ,
255+ { location : "HEADER" , parameterName : "Authorization" , prefix : "Bearer" } ,
256+ ] ,
257+ [
258+ "openApiSchema with an API key query parameter" ,
259+ {
260+ name : "weather" ,
261+ targetType : "openApiSchema" ,
262+ schemaSource : { inline : { path : "openapi.json" } } ,
263+ } ,
264+ { location : "QUERY_PARAMETER" , parameterName : "api_key" } ,
265+ ] ,
266+ [
267+ "apiGateway with a custom API key header" ,
268+ {
269+ name : "pets" ,
270+ targetType : "apiGateway" ,
271+ apiGateway : {
272+ restApiId : "api" ,
273+ stage : "prod" ,
274+ apiGatewayToolConfiguration : { toolFilters : [ { filterPath : "/*" , methods : [ "GET" ] } ] } ,
275+ } ,
276+ } ,
277+ { parameterName : "X-Pets-Key" } ,
278+ ] ,
279+ [
280+ "a query parameter name with a colon" ,
281+ githubPassthrough ,
282+ { location : "QUERY_PARAMETER" , parameterName : "api:key" } ,
283+ ] ,
284+ [
285+ "a query parameter name with brackets" ,
286+ githubPassthrough ,
287+ { location : "QUERY_PARAMETER" , parameterName : "auth[token]" } ,
288+ ] ,
289+ ] ) ( "accepts %s" , ( _case , target , placement ) => {
290+ expect (
291+ targetIssues ( { ...target , outboundAuth : { ...apiKeyAuth , ...placement } } ) ,
292+ ) . toBeUndefined ( ) ;
293+ } ) ;
294+ it . each ( [
295+ [
296+ "an unknown API key location" ,
297+ { location : "BODY" } ,
298+ [ "outboundAuth" , "location" ] ,
299+ 'Invalid option: expected one of "HEADER"|"QUERY_PARAMETER"' ,
300+ ] ,
301+ [
302+ "a parameterName over 64 characters" ,
303+ { parameterName : "x" . repeat ( 65 ) } ,
304+ [ "outboundAuth" , "parameterName" ] ,
305+ "Too big: expected string to have <=64 characters" ,
306+ ] ,
307+ [
308+ "a query parameter name over 64 characters" ,
309+ { location : "QUERY_PARAMETER" , parameterName : "x" . repeat ( 65 ) } ,
310+ [ "outboundAuth" , "parameterName" ] ,
311+ "Too big: expected string to have <=64 characters" ,
312+ ] ,
313+ [
314+ "a HEADER parameterName with a colon" ,
315+ { location : "HEADER" , parameterName : "Authorization: " } ,
316+ [ "outboundAuth" , "parameterName" ] ,
317+ "A HEADER API key's parameterName must be an HTTP header name, with no spaces or colons." ,
318+ ] ,
319+ [
320+ "a parameterName with spaces when location defaults to HEADER" ,
321+ { parameterName : "x api key" } ,
322+ [ "outboundAuth" , "parameterName" ] ,
323+ "A HEADER API key's parameterName must be an HTTP header name, with no spaces or colons." ,
324+ ] ,
325+ [
326+ "an empty API key prefix" ,
327+ { prefix : "" } ,
328+ [ "outboundAuth" , "prefix" ] ,
329+ "Too small: expected string to have >=1 characters" ,
330+ ] ,
331+ [
332+ "an API key prefix over 64 characters" ,
333+ { prefix : "x" . repeat ( 65 ) } ,
334+ [ "outboundAuth" , "prefix" ] ,
335+ "Too big: expected string to have <=64 characters" ,
336+ ] ,
337+ [
338+ "QUERY_PARAMETER without a parameterName" ,
339+ { location : "QUERY_PARAMETER" } ,
340+ [ "outboundAuth" , "parameterName" ] ,
341+ "An API key with location QUERY_PARAMETER requires a parameterName." ,
342+ ] ,
343+ ] ) ( "rejects %s" , ( _case , placement , path , message ) => {
344+ expect (
345+ targetIssues ( { ...githubPassthrough , outboundAuth : { ...apiKeyAuth , ...placement } } ) ,
346+ ) . toEqual ( [ { path, message } ] ) ;
347+ } ) ;
348+ it ( "rejects API key placement fields with non-API_KEY outbound auth" , ( ) => {
349+ expect (
350+ targetIssues ( {
351+ ...githubPassthrough ,
352+ outboundAuth : {
353+ type : "OAUTH" ,
354+ credentialName : "githubOauth" ,
355+ parameterName : "Authorization" ,
356+ } ,
357+ } ) ,
358+ ) . toEqual ( [
359+ {
360+ path : [ "outboundAuth" ] ,
361+ message : "location, parameterName and prefix are only valid with API_KEY outbound auth." ,
362+ } ,
363+ ] ) ;
364+ } ) ;
239365 it ( "requires JWT configuration when a gateway selects CUSTOM_JWT" , ( ) => {
240366 const gateway = { name : "gateway" , targets : [ ] } ;
241367 expect (
0 commit comments