@@ -2668,3 +2668,125 @@ dynamic_endpoints:
26682668 })
26692669 }
26702670}
2671+
2672+ // Default rules (retry on 429/5xx, fail on >= 400) historically applied only to
2673+ // endpoint, setup, and teardown responses. Auth sequence call responses were
2674+ // skipped, so an OAuth token endpoint returning 401 {"error":"invalid_client"}
2675+ // fell through to processors and died with cryptic "object has no member
2676+ // 'access_token'" instead of failing loudly on the 401.
2677+ func TestSpecAuthSequenceGetsDefaultRules (t * testing.T ) {
2678+ spec := `
2679+ name: "Auth Sequence Default Rules"
2680+
2681+ defaults:
2682+ state:
2683+ base_url: "https://api.example.com"
2684+ request:
2685+ headers:
2686+ Authorization: "Bearer {state.token}"
2687+
2688+ authentication:
2689+ type: "sequence"
2690+ expires: 270
2691+ sequence:
2692+ - request:
2693+ url: "https://auth.example.com/token"
2694+ method: POST
2695+ headers:
2696+ Content-Type: "application/x-www-form-urlencoded"
2697+ payload: "grant_type=client_credentials&client_id=x&client_secret=y"
2698+ response:
2699+ processors:
2700+ - expression: "response.json.access_token"
2701+ output: "state.token"
2702+ aggregation: last
2703+
2704+ endpoints:
2705+ things:
2706+ request:
2707+ url: "{state.base_url}/things"
2708+ response:
2709+ records:
2710+ jmespath: "data[]"
2711+ `
2712+
2713+ s , err := LoadSpec (spec )
2714+ require .NoError (t , err )
2715+
2716+ require .NotNil (t , s .Authentication )
2717+ require .Equal (t , AuthTypeSequence , s .Authentication .Type ())
2718+
2719+ rawSeq , ok := s .Authentication ["sequence" ]
2720+ require .True (t , ok , "spec authentication.sequence must be present after compile" )
2721+ authSeq := parseSequenceFromInterface (rawSeq )
2722+ require .Len (t , authSeq , 1 )
2723+
2724+ rules := authSeq [0 ].Response .Rules
2725+ require .GreaterOrEqual (t , len (rules ), 3 , "auth sequence response should have at least 3 default rules appended" )
2726+ assert .Equal (t , "Default Retry Rule" , rules [len (rules )- 3 ].Message )
2727+ assert .Equal (t , "JSON-RPC error response (HTTP 200 with error envelope)" , rules [len (rules )- 2 ].Message )
2728+ assert .Equal (t , "Default Fail Rule" , rules [len (rules )- 1 ].Message )
2729+ assert .Equal (t , "response.status >= 400" , rules [len (rules )- 1 ].Condition )
2730+ }
2731+
2732+ // Compiling the same spec twice (or two endpoints sharing spec.Defaults) must
2733+ // not stack the default rules — checkResponse should be idempotent.
2734+ func TestSpecDefaultRulesIdempotent (t * testing.T ) {
2735+ spec := `
2736+ name: "Idempotent Default Rules"
2737+
2738+ defaults:
2739+ state:
2740+ base_url: "https://api.example.com"
2741+
2742+ authentication:
2743+ type: "sequence"
2744+ sequence:
2745+ - request:
2746+ url: "https://auth.example.com/token"
2747+ method: POST
2748+ response:
2749+ processors:
2750+ - expression: "response.json.access_token"
2751+ output: "state.token"
2752+ aggregation: last
2753+
2754+ endpoints:
2755+ a:
2756+ request:
2757+ url: "{state.base_url}/a"
2758+ response:
2759+ records:
2760+ jmespath: "data[]"
2761+ b:
2762+ request:
2763+ url: "{state.base_url}/b"
2764+ response:
2765+ records:
2766+ jmespath: "data[]"
2767+ `
2768+
2769+ s , err := LoadSpec (spec )
2770+ require .NoError (t , err )
2771+
2772+ // Recompile both endpoints — simulates being run more than once (e.g. via
2773+ // ReadDataflow -> CompileSpecEndpoints / compileSpecEndpoint paths).
2774+ for _ , name := range []string {"a" , "b" } {
2775+ ep := s .EndpointMap [name ]
2776+ require .NoError (t , compileSpecEndpoint (& ep , s ))
2777+ s .EndpointMap [name ] = ep
2778+ }
2779+
2780+ for _ , name := range []string {"a" , "b" } {
2781+ ep := s .EndpointMap [name ]
2782+ // Endpoint response should have exactly 3 default rules (retry, jsonrpc, fail).
2783+ assert .Equal (t , 3 , len (ep .Response .Rules ), "endpoint %s should have exactly 3 default rules, got %d" , name , len (ep .Response .Rules ))
2784+ }
2785+
2786+ // Spec-level auth sequence should also have exactly 3 default rules
2787+ // despite being processed multiple times via compileSpecEndpoint above.
2788+ require .NotNil (t , s .Authentication )
2789+ authSeq := parseSequenceFromInterface (s .Authentication ["sequence" ])
2790+ require .Len (t , authSeq , 1 )
2791+ assert .Equal (t , 3 , len (authSeq [0 ].Response .Rules ), "spec auth sequence should have exactly 3 default rules across multiple compiles, got %d" , len (authSeq [0 ].Response .Rules ))
2792+ }
0 commit comments