TestHTTP_Token_AuthCode covers the happy path: POST /oauth2/token with an auth_code grant + PKCE verifier yields an access+refresh token JSON envelope with the right fields and the no-store header.
(t *testing.T)
| 155 | // with an auth_code grant + PKCE verifier yields an access+refresh |
| 156 | // token JSON envelope with the right fields and the no-store header. |
| 157 | func TestHTTP_Token_AuthCode(t *testing.T) { |
| 158 | server, provider, _, clientID, userID := setupOAuthAPI(t) |
| 159 | redirectURI := "http://localhost:8765/callback" |
| 160 | verifier, challenge := newPKCE(t) |
| 161 | code := mintAuthCode(t, provider, clientID, userID, redirectURI, challenge) |
| 162 | |
| 163 | form := url.Values{} |
| 164 | form.Set("grant_type", "authorization_code") |
| 165 | form.Set("code", code) |
| 166 | form.Set("client_id", clientID) |
| 167 | form.Set("redirect_uri", redirectURI) |
| 168 | form.Set("code_verifier", verifier) |
| 169 | |
| 170 | resp, err := http.Post(server.URL+"/oauth2/token", |
| 171 | "application/x-www-form-urlencoded", strings.NewReader(form.Encode())) |
| 172 | if err != nil { |
| 173 | t.Fatal(err) |
| 174 | } |
| 175 | defer resp.Body.Close() |
| 176 | if resp.StatusCode != http.StatusOK { |
| 177 | t.Fatalf("status = %d, want 200", resp.StatusCode) |
| 178 | } |
| 179 | // RFC 6749 §5.1: token response MUST include Cache-Control: no-store. |
| 180 | if cc := resp.Header.Get("Cache-Control"); !strings.Contains(cc, "no-store") { |
| 181 | t.Errorf("Cache-Control = %q, want no-store", cc) |
| 182 | } |
| 183 | if ct := resp.Header.Get("Content-Type"); !strings.HasPrefix(ct, "application/json") { |
| 184 | t.Errorf("Content-Type = %q, want application/json", ct) |
| 185 | } |
| 186 | |
| 187 | var body struct { |
| 188 | AccessToken string `json:"access_token"` |
| 189 | TokenType string `json:"token_type"` |
| 190 | ExpiresIn int `json:"expires_in"` |
| 191 | RefreshToken string `json:"refresh_token"` |
| 192 | Scope string `json:"scope"` |
| 193 | } |
| 194 | if err := json.NewDecoder(resp.Body).Decode(&body); err != nil { |
| 195 | t.Fatal(err) |
| 196 | } |
| 197 | if !strings.HasPrefix(body.AccessToken, oauth.AccessTokenPrefix) { |
| 198 | t.Errorf("access_token missing %q prefix: %q", oauth.AccessTokenPrefix, body.AccessToken) |
| 199 | } |
| 200 | if !strings.HasPrefix(body.RefreshToken, oauth.RefreshTokenPrefix) { |
| 201 | t.Errorf("refresh_token missing %q prefix: %q", oauth.RefreshTokenPrefix, body.RefreshToken) |
| 202 | } |
| 203 | if body.TokenType != "bearer" { |
| 204 | t.Errorf("token_type = %q, want bearer", body.TokenType) |
| 205 | } |
| 206 | if body.ExpiresIn <= 0 { |
| 207 | t.Errorf("expires_in = %d, want > 0", body.ExpiresIn) |
| 208 | } |
| 209 | } |
| 210 | |
| 211 | // TestHTTP_Token_RefreshGrant: exchange auth_code, then exchange the |
| 212 | // refresh token for a new pair. Verifies refresh rotation works through |
nothing calls this directly
no test coverage detected