Skip to content

Commit 0e53033

Browse files
committed
test(auth): simplify credential regression coverage
1 parent e754fb3 commit 0e53033

4 files changed

Lines changed: 70 additions & 181 deletions

File tree

‎packages/cli/test/e2e/auth.test.ts‎

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -146,11 +146,8 @@ describe("sentry auth whoami", () => {
146146
expect(result.exitCode).toBe(EXIT.AUTH_NOT_AUTHENTICATED);
147147
});
148148

149-
test.each([
150-
TEST_TOKEN,
151-
` \n${TEST_TOKEN}\r\t`,
152-
])("shows current user identity with surrounding token whitespace %#", async (token) => {
153-
await ctx.setAuthToken(token);
149+
test("shows current user identity", async () => {
150+
await ctx.setAuthToken(TEST_TOKEN);
154151

155152
const result = await ctx.run(["auth", "whoami"]);
156153

‎packages/cli/test/lib/db/auth.property.test.ts‎

Lines changed: 5 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
*
44
* Verifies invariants that must hold for any valid token values:
55
* - SENTRY_AUTH_TOKEN always takes priority over SENTRY_TOKEN
6-
* - Env vars always take priority over stored tokens
6+
* - Stored OAuth takes priority unless SENTRY_FORCE_ENV_TOKEN is set
77
* - Env tokens never trigger refresh
88
* - AuthConfig.source correctly identifies the origin
99
*/
@@ -16,7 +16,7 @@ import {
1616
string,
1717
stringMatching,
1818
} from "fast-check";
19-
import { afterEach, beforeEach, describe, expect, test } from "vitest";
19+
import { describe, expect, test } from "vitest";
2020
import {
2121
type AuthSource,
2222
getAuthConfig,
@@ -27,10 +27,11 @@ import {
2727
resetAuthTokenCache,
2828
setAuthToken,
2929
} from "../../../src/lib/db/auth.js";
30-
import { useTestConfigDir } from "../../helpers.js";
30+
import { useEnvSandbox, useTestConfigDir } from "../../helpers.js";
3131
import { DEFAULT_NUM_RUNS } from "../../model-based/helpers.js";
3232

3333
useTestConfigDir("auth-prop-");
34+
useEnvSandbox(["SENTRY_AUTH_TOKEN", "SENTRY_TOKEN", "SENTRY_FORCE_ENV_TOKEN"]);
3435

3536
/** Arbitrary for non-empty, trimmed token strings */
3637
const tokenArb = string({ minLength: 1, maxLength: 100 }).filter(
@@ -40,32 +41,6 @@ const tokenArb = string({ minLength: 1, maxLength: 100 }).filter(
4041
/** Stored tokens must satisfy the persistence boundary; malformed inputs have separate coverage. */
4142
const storedTokenArb = stringMatching(/^[\x21-\x7e]{1,100}$/);
4243

43-
/** Save and restore env vars around each test */
44-
let savedAuthToken: string | undefined;
45-
let savedSentryToken: string | undefined;
46-
47-
beforeEach(() => {
48-
savedAuthToken = process.env.SENTRY_AUTH_TOKEN;
49-
savedSentryToken = process.env.SENTRY_TOKEN;
50-
delete process.env.SENTRY_AUTH_TOKEN;
51-
delete process.env.SENTRY_TOKEN;
52-
resetAuthTokenCache();
53-
resetAuthRowCache();
54-
});
55-
56-
afterEach(() => {
57-
if (savedAuthToken !== undefined) {
58-
process.env.SENTRY_AUTH_TOKEN = savedAuthToken;
59-
} else {
60-
delete process.env.SENTRY_AUTH_TOKEN;
61-
}
62-
if (savedSentryToken !== undefined) {
63-
process.env.SENTRY_TOKEN = savedSentryToken;
64-
} else {
65-
delete process.env.SENTRY_TOKEN;
66-
}
67-
});
68-
6944
/** Invalidate between property iterations — env-var mutations bypass setAuthToken. */
7045
function resetAuthCaches() {
7146
resetAuthTokenCache();
@@ -99,6 +74,7 @@ describe("property: env var priority", () => {
9974
// Stored OAuth takes priority — env token is for build tooling
10075
expect(getAuthToken()).toBe(storedToken);
10176
expect(getAuthConfig()?.source).toBe("oauth" satisfies AuthSource);
77+
expect(isEnvTokenActive()).toBe(true);
10278
}),
10379
{ numRuns: DEFAULT_NUM_RUNS }
10480
);
@@ -196,22 +172,6 @@ describe("property: isEnvTokenActive consistency", () => {
196172
{ numRuns: DEFAULT_NUM_RUNS }
197173
);
198174
});
199-
200-
test("stored OAuth takes priority: getAuthConfig returns oauth even when env token is set", () => {
201-
fcAssert(
202-
property(tokenArb, storedTokenArb, (envToken, storedToken) => {
203-
resetAuthCaches();
204-
process.env.SENTRY_AUTH_TOKEN = envToken;
205-
setAuthToken(storedToken);
206-
207-
const config = getAuthConfig();
208-
expect(config?.source).toBe("oauth");
209-
// But isEnvTokenActive is still true (env token exists)
210-
expect(isEnvTokenActive()).toBe(true);
211-
}),
212-
{ numRuns: DEFAULT_NUM_RUNS }
213-
);
214-
});
215175
});
216176

217177
describe("property: source round-trip", () => {

‎packages/cli/test/lib/db/auth.test.ts‎

Lines changed: 3 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
* by property tests (isAuthenticated, getActiveEnvVarName).
88
*/
99

10-
import { afterEach, beforeEach, describe, expect, test } from "vitest";
10+
import { describe, expect, test } from "vitest";
1111
import {
1212
ANON_IDENTITY,
1313
clearAuth,
@@ -20,43 +20,17 @@ import {
2020
isAuthenticated,
2121
isEnvTokenActive,
2222
refreshToken,
23-
resetAuthRowCache,
2423
resetAuthTokenCache,
2524
resetHasStoredCredsCache,
2625
resetIdentityFingerprintCache,
2726
setAuthToken,
2827
} from "../../../src/lib/db/auth.js";
2928
import { getDatabase } from "../../../src/lib/db/index.js";
3029
import { MalformedAuthTokenError } from "../../../src/lib/errors.js";
31-
import { useTestConfigDir } from "../../helpers.js";
30+
import { useEnvSandbox, useTestConfigDir } from "../../helpers.js";
3231

3332
useTestConfigDir("auth-env-");
34-
35-
let savedAuthToken: string | undefined;
36-
let savedSentryToken: string | undefined;
37-
38-
beforeEach(() => {
39-
savedAuthToken = process.env.SENTRY_AUTH_TOKEN;
40-
savedSentryToken = process.env.SENTRY_TOKEN;
41-
delete process.env.SENTRY_AUTH_TOKEN;
42-
delete process.env.SENTRY_TOKEN;
43-
resetIdentityFingerprintCache();
44-
resetAuthTokenCache();
45-
resetAuthRowCache();
46-
});
47-
48-
afterEach(() => {
49-
if (savedAuthToken !== undefined) {
50-
process.env.SENTRY_AUTH_TOKEN = savedAuthToken;
51-
} else {
52-
delete process.env.SENTRY_AUTH_TOKEN;
53-
}
54-
if (savedSentryToken !== undefined) {
55-
process.env.SENTRY_TOKEN = savedSentryToken;
56-
} else {
57-
delete process.env.SENTRY_TOKEN;
58-
}
59-
});
33+
useEnvSandbox(["SENTRY_AUTH_TOKEN", "SENTRY_TOKEN", "SENTRY_FORCE_ENV_TOKEN"]);
6034

6135
describe("env var auth: getAuthToken edge cases", () => {
6236
test("ignores empty SENTRY_AUTH_TOKEN", () => {
@@ -106,16 +80,6 @@ describe("env var auth: isEnvTokenActive edge case", () => {
10680
});
10781

10882
describe("env var auth: getActiveEnvVarName", () => {
109-
test("returns SENTRY_AUTH_TOKEN when that var is set", () => {
110-
process.env.SENTRY_AUTH_TOKEN = "test_token";
111-
expect(getActiveEnvVarName()).toBe("SENTRY_AUTH_TOKEN");
112-
});
113-
114-
test("returns SENTRY_TOKEN when only that var is set", () => {
115-
process.env.SENTRY_TOKEN = "test_token";
116-
expect(getActiveEnvVarName()).toBe("SENTRY_TOKEN");
117-
});
118-
11983
test("prefers SENTRY_AUTH_TOKEN when both are set", () => {
12084
process.env.SENTRY_AUTH_TOKEN = "primary";
12185
process.env.SENTRY_TOKEN = "secondary";
@@ -197,11 +161,6 @@ describe("env var auth: getRawEnvToken", () => {
197161
expect(getActiveEnvVarName()).toBe("SENTRY_TOKEN");
198162
});
199163

200-
test("returns SENTRY_TOKEN when SENTRY_AUTH_TOKEN is unset", () => {
201-
process.env.SENTRY_TOKEN = "fallback_token";
202-
expect(getRawEnvToken()).toBe("fallback_token");
203-
});
204-
205164
test("returns undefined when no env var is set", () => {
206165
expect(getRawEnvToken()).toBeUndefined();
207166
});

‎packages/cli/test/lib/sentry-client.auth.test.ts‎

Lines changed: 60 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -58,18 +58,24 @@ describe("authenticated fetch bearer validation", () => {
5858
let originalFetch: typeof globalThis.fetch;
5959
let requests: { url: string; authorization: string | null }[];
6060

61+
/** Mock responses while recording the actual request URL and Authorization. */
62+
function mockResponses(
63+
respond: (url: string, authorization: string | null) => Response
64+
): typeof fetch {
65+
return mockFetch((input, init) => {
66+
const url = extractFetchUrl(input);
67+
const authorization = new Headers(init?.headers).get("Authorization");
68+
requests.push({ url, authorization });
69+
return Promise.resolve(respond(url, authorization));
70+
});
71+
}
72+
6173
beforeEach(async () => {
6274
await resetHostScopingState();
6375
resetAuthenticatedFetch();
6476
originalFetch = globalThis.fetch;
6577
requests = [];
66-
globalThis.fetch = mockFetch((input, init) => {
67-
requests.push({
68-
url: extractFetchUrl(input),
69-
authorization: new Headers(init?.headers).get("Authorization"),
70-
});
71-
return Promise.resolve(new Response("{}", { status: 200 }));
72-
});
78+
globalThis.fetch = mockResponses(() => new Response("{}", { status: 200 }));
7379
});
7480

7581
afterEach(async () => {
@@ -220,23 +226,17 @@ describe("authenticated fetch bearer validation", () => {
220226

221227
test("stores Vary: Authorization with the credential actually sent", async () => {
222228
storeLegacyToken("\x1fsynthetic-token\x7f");
223-
globalThis.fetch = mockFetch((input, init) => {
224-
requests.push({
225-
url: extractFetchUrl(input),
226-
authorization: new Headers(init?.headers).get("Authorization"),
227-
});
228-
return Promise.resolve(
229-
Response.json(
230-
{ source: "network" },
231-
{
232-
headers: {
233-
"Cache-Control": "private, max-age=300",
234-
Vary: "Authorization",
235-
},
236-
}
237-
)
238-
);
239-
});
229+
globalThis.fetch = mockResponses(() =>
230+
Response.json(
231+
{ source: "network" },
232+
{
233+
headers: {
234+
"Cache-Control": "private, max-age=300",
235+
Vary: "Authorization",
236+
},
237+
}
238+
)
239+
);
240240

241241
await request();
242242

@@ -260,7 +260,7 @@ describe("authenticated fetch bearer validation", () => {
260260
url: "https://other-sentry.example.com",
261261
org: "synthetic-org",
262262
});
263-
setAuthToken(` \n${token}\t `);
263+
storeLegacyToken(` \n${token}\t `);
264264
await expect(request()).rejects.toBeInstanceOf(HostScopeError);
265265
expect(requests).toEqual([]);
266266
});
@@ -319,24 +319,17 @@ describe("authenticated fetch bearer validation", () => {
319319
])("retries with a normalized valid refreshed bearer %#", async (token) => {
320320
process.env.SENTRY_CLIENT_ID = "synthetic-client-id";
321321
setAuthToken("stored-token", 3600, "synthetic-refresh-token");
322-
globalThis.fetch = mockFetch((input, init) => {
323-
const url = extractFetchUrl(input);
324-
const authorization = new Headers(init?.headers).get("Authorization");
325-
requests.push({ url, authorization });
322+
globalThis.fetch = mockResponses((url, authorization) => {
326323
if (url.endsWith("/oauth/token/")) {
327-
return Promise.resolve(
328-
Response.json({
329-
access_token: token,
330-
token_type: "bearer",
331-
expires_in: 3600,
332-
})
333-
);
324+
return Response.json({
325+
access_token: token,
326+
token_type: "bearer",
327+
expires_in: 3600,
328+
});
334329
}
335-
return Promise.resolve(
336-
new Response("{}", {
337-
status: authorization === "Bearer stored-token" ? 401 : 200,
338-
})
339-
);
330+
return new Response("{}", {
331+
status: authorization === "Bearer stored-token" ? 401 : 200,
332+
});
340333
});
341334

342335
expect((await request()).status).toBe(200);
@@ -357,23 +350,16 @@ describe("authenticated fetch bearer validation", () => {
357350
])("rejects malformed refreshed credentials without retrying the request %#", async (token) => {
358351
process.env.SENTRY_CLIENT_ID = "synthetic-client-id";
359352
setAuthToken("stored-token", 3600, "synthetic-refresh-token");
360-
globalThis.fetch = mockFetch((input, init) => {
361-
const url = extractFetchUrl(input);
362-
requests.push({
363-
url,
364-
authorization: new Headers(init?.headers).get("Authorization"),
365-
});
353+
globalThis.fetch = mockResponses((url) => {
366354
if (url.endsWith("/oauth/token/")) {
367-
return Promise.resolve(
368-
Response.json({
369-
access_token: token,
370-
token_type: "bearer",
371-
expires_in: 3600,
372-
refresh_token: "synthetic-refresh-token",
373-
})
374-
);
355+
return Response.json({
356+
access_token: token,
357+
token_type: "bearer",
358+
expires_in: 3600,
359+
refresh_token: "synthetic-refresh-token",
360+
});
375361
}
376-
return Promise.resolve(new Response("{}", { status: 401 }));
362+
return new Response("{}", { status: 401 });
377363
});
378364

379365
for (let attempt = 0; attempt < 2; attempt++) {
@@ -408,23 +394,16 @@ describe("authenticated fetch bearer validation", () => {
408394
process.env[source] = MALFORMED_TOKEN;
409395
}
410396
setAuthToken("expired-token", -1, "synthetic-refresh-token");
411-
globalThis.fetch = mockFetch((input, init) => {
412-
const url = extractFetchUrl(input);
413-
requests.push({
414-
url,
415-
authorization: new Headers(init?.headers).get("Authorization"),
416-
});
417-
return Promise.resolve(
418-
url.endsWith("/oauth/token/")
419-
? Response.json({
420-
access_token: "\x1f \nrefreshed-token\r\t\x7f",
421-
token_type: "bearer",
422-
expires_in: 3600,
423-
refresh_token: "replacement-refresh-token",
424-
})
425-
: Response.json({})
426-
);
427-
});
397+
globalThis.fetch = mockResponses((url) =>
398+
url.endsWith("/oauth/token/")
399+
? Response.json({
400+
access_token: "\x1f \nrefreshed-token\r\t\x7f",
401+
token_type: "bearer",
402+
expires_in: 3600,
403+
refresh_token: "replacement-refresh-token",
404+
})
405+
: Response.json({})
406+
);
428407

429408
expect((await request()).status).toBe(200);
430409
expect(getAuthConfig()).toMatchObject({
@@ -440,20 +419,14 @@ describe("authenticated fetch bearer validation", () => {
440419
test("rejects malformed proactive refresh before storing or using the token", async () => {
441420
process.env.SENTRY_CLIENT_ID = "synthetic-client-id";
442421
setAuthToken("expired-token", -1, "synthetic-refresh-token");
443-
globalThis.fetch = mockFetch((input, init) => {
444-
requests.push({
445-
url: extractFetchUrl(input),
446-
authorization: new Headers(init?.headers).get("Authorization"),
447-
});
448-
return Promise.resolve(
449-
Response.json({
450-
access_token: "opaque-\0-secret-tail",
451-
token_type: "bearer",
452-
expires_in: 3600,
453-
refresh_token: "replacement-refresh-token",
454-
})
455-
);
456-
});
422+
globalThis.fetch = mockResponses(() =>
423+
Response.json({
424+
access_token: "opaque-\0-secret-tail",
425+
token_type: "bearer",
426+
expires_in: 3600,
427+
refresh_token: "replacement-refresh-token",
428+
})
429+
);
457430

458431
await expect(request()).rejects.toMatchObject({
459432
reason: "invalid",

0 commit comments

Comments
 (0)