| 1110 | } |
| 1111 | |
| 1112 | func TestMarkKeyOnError(t *testing.T) { |
| 1113 | t.Parallel() |
| 1114 | |
| 1115 | tests := []struct { |
| 1116 | name string |
| 1117 | err error |
| 1118 | expectedReturn bool |
| 1119 | expectedState keypool.KeyState |
| 1120 | }{ |
| 1121 | { |
| 1122 | // Not an *anthropic.Error: no status code to act on. |
| 1123 | name: "non_api_error_returns_false", |
| 1124 | err: xerrors.New("network failure"), |
| 1125 | expectedReturn: false, |
| 1126 | expectedState: keypool.KeyStateValid, |
| 1127 | }, |
| 1128 | { |
| 1129 | // Rate-limited: temporary cooldown. |
| 1130 | name: "429_marks_temporary", |
| 1131 | err: &anthropic.Error{StatusCode: http.StatusTooManyRequests, Response: &http.Response{StatusCode: http.StatusTooManyRequests}}, |
| 1132 | expectedReturn: true, |
| 1133 | expectedState: keypool.KeyStateTemporary, |
| 1134 | }, |
| 1135 | { |
| 1136 | // Auth failure: mark permanent. |
| 1137 | name: "401_marks_permanent", |
| 1138 | err: &anthropic.Error{StatusCode: http.StatusUnauthorized, Response: &http.Response{StatusCode: http.StatusUnauthorized}}, |
| 1139 | expectedReturn: true, |
| 1140 | expectedState: keypool.KeyStatePermanent, |
| 1141 | }, |
| 1142 | { |
| 1143 | // Auth forbidden: mark permanent. |
| 1144 | name: "403_marks_permanent", |
| 1145 | err: &anthropic.Error{StatusCode: http.StatusForbidden, Response: &http.Response{StatusCode: http.StatusForbidden}}, |
| 1146 | expectedReturn: true, |
| 1147 | expectedState: keypool.KeyStatePermanent, |
| 1148 | }, |
| 1149 | { |
| 1150 | // Server errors are not key-specific. |
| 1151 | name: "500_does_not_mark", |
| 1152 | err: &anthropic.Error{StatusCode: http.StatusInternalServerError, Response: &http.Response{StatusCode: http.StatusInternalServerError}}, |
| 1153 | expectedReturn: false, |
| 1154 | expectedState: keypool.KeyStateValid, |
| 1155 | }, |
| 1156 | } |
| 1157 | |
| 1158 | for _, tc := range tests { |
| 1159 | t.Run(tc.name, func(t *testing.T) { |
| 1160 | t.Parallel() |
| 1161 | pool, err := keypool.New([]string{"key-0"}, quartz.NewMock(t)) |
| 1162 | require.NoError(t, err) |
| 1163 | key, keyPoolErr := pool.Walker().Next() |
| 1164 | require.Nil(t, keyPoolErr) |
| 1165 | |
| 1166 | base := &interceptionBase{cfg: config.Anthropic{KeyPool: pool}, logger: slog.Make()} |
| 1167 | |
| 1168 | got := base.markKeyOnError(context.Background(), key, tc.err) |
| 1169 | assert.Equal(t, tc.expectedReturn, got) |