Skip to content

Commit f0069c8

Browse files
committed
Merge branch 'main' into feat/team-list
2 parents 48165fb + baaa3d3 commit f0069c8

File tree

18 files changed

+202
-255
lines changed

18 files changed

+202
-255
lines changed

AGENTS.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -383,6 +383,37 @@ Minimal comments, maximum clarity. Comments explain **intent and reasoning**, no
383383
| Unit tests | `*.test.ts` | `test/` (mirrors `src/`) |
384384
| E2E tests | `*.test.ts` | `test/e2e/` |
385385

386+
### Test Environment Isolation (CRITICAL)
387+
388+
Tests that need a database or config directory **must** use `useTestConfigDir()` from `test/helpers.ts`. This helper:
389+
- Creates a unique temp directory in `beforeEach`
390+
- Sets `SENTRY_CONFIG_DIR` to point at it
391+
- **Restores** (never deletes) the env var in `afterEach`
392+
- Closes the database and cleans up temp files
393+
394+
**NEVER** do any of these in test files:
395+
- `delete process.env.SENTRY_CONFIG_DIR`This pollutes other test files that load after yours
396+
- `const baseDir = process.env[CONFIG_DIR_ENV_VAR]!` at module scopeThis captures a value that may be stale
397+
- Manual `beforeEach`/`afterEach` that sets/deletes `SENTRY_CONFIG_DIR`
398+
399+
**Why**: Bun runs test files **sequentially in one thread** (loadrun all testsload next file). If your `afterEach` deletes the env var, the next file's module-level code reads `undefined`, causing `TypeError: The "paths[0]" property must be of type string`.
400+
401+
```typescript
402+
// CORRECT: Use the helper
403+
import { useTestConfigDir } from "../helpers.js";
404+
405+
const getConfigDir = useTestConfigDir("my-test-prefix-");
406+
407+
// If you need the directory path in a test:
408+
test("example", () => {
409+
const dir = getConfigDir();
410+
});
411+
412+
// WRONG: Manual env var management
413+
beforeEach(() => { process.env.SENTRY_CONFIG_DIR = tmpDir; });
414+
afterEach(() => { delete process.env.SENTRY_CONFIG_DIR; }); // BUG!
415+
```
416+
386417
### Property-Based Testing
387418

388419
Use property-based tests when verifying invariants that should hold for **any valid input**.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@
5959
"engines": {
6060
"node": ">=22"
6161
},
62-
"packageManager": "bun@1.3.3",
62+
"packageManager": "bun@1.3.9",
6363
"patchedDependencies": {
6464
"@stricli/core@1.2.5": "patches/@stricli%2Fcore@1.2.5.patch",
6565
"@sentry/core@10.38.0": "patches/@sentry%2Fcore@10.38.0.patch"

src/commands/cli/setup.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ function printWelcomeMessage(
171171
log(`Installed sentry v${version} to ${binaryPath}`);
172172
log("");
173173
log("Get started:");
174-
log(" sentry login Authenticate with Sentry");
174+
log(" sentry auth login Authenticate with Sentry");
175175
log(" sentry --help See all available commands");
176176
log("");
177177
log("https://cli.sentry.dev");

src/lib/oauth.ts

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,20 @@ const SENTRY_URL = process.env.SENTRY_URL ?? "https://sentry.io";
2424
* Build-time: Injected via Bun.build({ define: { SENTRY_CLIENT_ID: "..." } })
2525
* Runtime: Can be overridden via SENTRY_CLIENT_ID env var (for self-hosted)
2626
*
27+
* Read at call time (not module load time) so tests can set process.env.SENTRY_CLIENT_ID
28+
* after module initialization.
29+
*
2730
* @see script/build.ts
2831
*/
2932
declare const SENTRY_CLIENT_ID_BUILD: string | undefined;
30-
const SENTRY_CLIENT_ID =
31-
process.env.SENTRY_CLIENT_ID ??
32-
(typeof SENTRY_CLIENT_ID_BUILD !== "undefined" ? SENTRY_CLIENT_ID_BUILD : "");
33+
function getClientId(): string {
34+
return (
35+
process.env.SENTRY_CLIENT_ID ??
36+
(typeof SENTRY_CLIENT_ID_BUILD !== "undefined"
37+
? SENTRY_CLIENT_ID_BUILD
38+
: "")
39+
);
40+
}
3341

3442
// OAuth scopes requested for the CLI
3543
const SCOPES = [
@@ -85,7 +93,8 @@ async function fetchWithConnectionError(
8593

8694
/** Request a device code from Sentry's device authorization endpoint */
8795
function requestDeviceCode() {
88-
if (!SENTRY_CLIENT_ID) {
96+
const clientId = getClientId();
97+
if (!clientId) {
8998
throw new ConfigError(
9099
"SENTRY_CLIENT_ID is required for authentication",
91100
"Set SENTRY_CLIENT_ID environment variable or use a pre-built binary"
@@ -99,7 +108,7 @@ function requestDeviceCode() {
99108
method: "POST",
100109
headers: { "Content-Type": "application/x-www-form-urlencoded" },
101110
body: new URLSearchParams({
102-
client_id: SENTRY_CLIENT_ID,
111+
client_id: clientId,
103112
scope: SCOPES,
104113
}),
105114
}
@@ -142,7 +151,7 @@ function pollForToken(deviceCode: string): Promise<TokenResponse> {
142151
method: "POST",
143152
headers: { "Content-Type": "application/x-www-form-urlencoded" },
144153
body: new URLSearchParams({
145-
client_id: SENTRY_CLIENT_ID,
154+
client_id: getClientId(),
146155
device_code: deviceCode,
147156
grant_type: "urn:ietf:params:oauth:grant-type:device_code",
148157
}),
@@ -313,7 +322,8 @@ export async function setApiToken(token: string): Promise<void> {
313322
export function refreshAccessToken(
314323
refreshToken: string
315324
): Promise<TokenResponse> {
316-
if (!SENTRY_CLIENT_ID) {
325+
const clientId = getClientId();
326+
if (!clientId) {
317327
throw new ConfigError(
318328
"SENTRY_CLIENT_ID is required for token refresh",
319329
"Set SENTRY_CLIENT_ID environment variable or use a pre-built binary"
@@ -327,7 +337,7 @@ export function refreshAccessToken(
327337
method: "POST",
328338
headers: { "Content-Type": "application/x-www-form-urlencoded" },
329339
body: new URLSearchParams({
330-
client_id: SENTRY_CLIENT_ID,
340+
client_id: clientId,
331341
grant_type: "refresh_token",
332342
refresh_token: refreshToken,
333343
}),

test/commands/cli/fix.test.ts

Lines changed: 12 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,16 @@
33
*/
44

55
import { Database } from "bun:sqlite";
6-
import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
7-
import { mkdirSync, rmSync } from "node:fs";
6+
import { describe, expect, mock, test } from "bun:test";
87
import { join } from "node:path";
98
import { fixCommand } from "../../../src/commands/cli/fix.js";
10-
import {
11-
CONFIG_DIR_ENV_VAR,
12-
closeDatabase,
13-
} from "../../../src/lib/db/index.js";
9+
import { closeDatabase } from "../../../src/lib/db/index.js";
1410
import {
1511
EXPECTED_TABLES,
1612
generatePreMigrationTableDDL,
1713
initSchema,
1814
} from "../../../src/lib/db/schema.js";
15+
import { useTestConfigDir } from "../../helpers.js";
1916

2017
/**
2118
* Generate DDL for creating a database with pre-migration tables.
@@ -64,46 +61,12 @@ function createDatabaseWithMissingTables(
6461
).run();
6562
}
6663

67-
let testDir: string;
68-
let originalConfigDir: string | undefined;
69-
70-
beforeEach(() => {
71-
// Save original config dir
72-
originalConfigDir = process.env[CONFIG_DIR_ENV_VAR];
73-
74-
// Close any existing database connection
75-
closeDatabase();
76-
77-
// Create unique test directory
78-
const baseDir = originalConfigDir ?? "/tmp/sentry-cli-test";
79-
testDir = join(
80-
baseDir,
81-
`fix-test-${Date.now()}-${Math.random().toString(36).slice(2)}`
82-
);
83-
mkdirSync(testDir, { recursive: true });
84-
process.env[CONFIG_DIR_ENV_VAR] = testDir;
85-
});
86-
87-
afterEach(() => {
88-
closeDatabase();
89-
// Restore original config dir
90-
if (originalConfigDir) {
91-
process.env[CONFIG_DIR_ENV_VAR] = originalConfigDir;
92-
} else {
93-
delete process.env[CONFIG_DIR_ENV_VAR];
94-
}
95-
// Clean up test directory
96-
try {
97-
rmSync(testDir, { recursive: true, force: true });
98-
} catch {
99-
// Ignore cleanup errors
100-
}
101-
});
64+
const getTestDir = useTestConfigDir("fix-test-");
10265

10366
describe("sentry cli fix", () => {
10467
test("reports no issues for healthy database", async () => {
10568
// Create healthy database
106-
const db = new Database(join(testDir, "cli.db"));
69+
const db = new Database(join(getTestDir(), "cli.db"));
10770
initSchema(db);
10871
db.close();
10972

@@ -124,7 +87,7 @@ describe("sentry cli fix", () => {
12487

12588
test("detects and reports missing columns in dry-run mode", async () => {
12689
// Create database with pre-migration tables (missing v4 columns)
127-
const db = new Database(join(testDir, "cli.db"));
90+
const db = new Database(join(getTestDir(), "cli.db"));
12891
createPreMigrationDatabase(db);
12992
db.close();
13093

@@ -148,7 +111,7 @@ describe("sentry cli fix", () => {
148111

149112
test("fixes missing columns when not in dry-run mode", async () => {
150113
// Create database with pre-migration tables (missing v4 columns)
151-
const db = new Database(join(testDir, "cli.db"));
114+
const db = new Database(join(getTestDir(), "cli.db"));
152115
createPreMigrationDatabase(db);
153116
db.close();
154117

@@ -169,7 +132,7 @@ describe("sentry cli fix", () => {
169132

170133
// Verify the column was actually added
171134
closeDatabase();
172-
const verifyDb = new Database(join(testDir, "cli.db"));
135+
const verifyDb = new Database(join(getTestDir(), "cli.db"));
173136
const cols = verifyDb.query("PRAGMA table_info(dsn_cache)").all() as Array<{
174137
name: string;
175138
}>;
@@ -188,7 +151,7 @@ describe("sentry cli fix", () => {
188151
// that was previously missing tables (now fixed by auto-repair at startup).
189152
test("handles database that was auto-repaired at startup", async () => {
190153
// Create database missing dsn_cache - initSchema will create it when command runs
191-
const db = new Database(join(testDir, "cli.db"));
154+
const db = new Database(join(getTestDir(), "cli.db"));
192155
createDatabaseWithMissingTables(db, ["dsn_cache"]);
193156
db.close();
194157

@@ -210,7 +173,7 @@ describe("sentry cli fix", () => {
210173

211174
// Verify the table was created (by initSchema auto-repair)
212175
closeDatabase();
213-
const verifyDb = new Database(join(testDir, "cli.db"));
176+
const verifyDb = new Database(join(getTestDir(), "cli.db"));
214177
const tables = verifyDb
215178
.query(
216179
"SELECT name FROM sqlite_master WHERE type='table' AND name='dsn_cache'"
@@ -221,7 +184,7 @@ describe("sentry cli fix", () => {
221184
});
222185

223186
test("shows database path in output", async () => {
224-
const db = new Database(join(testDir, "cli.db"));
187+
const db = new Database(join(getTestDir(), "cli.db"));
225188
initSchema(db);
226189
db.close();
227190

@@ -237,6 +200,6 @@ describe("sentry cli fix", () => {
237200

238201
const output = stdoutWrite.mock.calls.map((c) => c[0]).join("");
239202
expect(output).toContain("Database:");
240-
expect(output).toContain(testDir);
203+
expect(output).toContain(getTestDir());
241204
});
242205
});

test/commands/cli/setup.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -378,7 +378,7 @@ describe("sentry cli setup", () => {
378378
// Should show welcome message, not "Setup complete!"
379379
expect(combined).toContain("Installed sentry v");
380380
expect(combined).toContain("Get started:");
381-
expect(combined).toContain("sentry login");
381+
expect(combined).toContain("sentry auth login");
382382
expect(combined).toContain("sentry --help");
383383
expect(combined).toContain("cli.sentry.dev");
384384
expect(combined).not.toContain("Setup complete!");

0 commit comments

Comments
 (0)