Skip to content

Commit 320be0d

Browse files
committed
fix(dbio/api): apply default rules to auth sequence responses
- ensure default retry/fail rules apply to authentication sequence responses - previously, auth sequence responses were skipped, causing unclear errors - verify rule application is idempotent to prevent stacking on re-compilation
1 parent 3ac4029 commit 320be0d

1 file changed

Lines changed: 122 additions & 0 deletions

File tree

core/dbio/api/spec_test.go

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)