@@ -10,12 +10,6 @@ import { afterEach, beforeEach, describe, expect, spyOn, test } from "bun:test";
1010import * as clack from "@clack/prompts" ;
1111// biome-ignore lint/performance/noNamespaceImport: spyOn requires object reference
1212import * as apiClient from "../../../src/lib/api-client.js" ;
13- // biome-ignore lint/performance/noNamespaceImport: spyOn requires object reference
14- import * as dbRegions from "../../../src/lib/db/regions.js" ;
15- // biome-ignore lint/performance/noNamespaceImport: spyOn requires object reference
16- import * as projectCache from "../../../src/lib/db/project-cache.js" ;
17- // biome-ignore lint/performance/noNamespaceImport: spyOn requires object reference
18- import * as dsnIndex from "../../../src/lib/dsn/index.js" ;
1913import { handleLocalOp } from "../../../src/lib/init/local-ops.js" ;
2014import type {
2115 CreateSentryProjectPayload ,
@@ -70,12 +64,6 @@ describe("create-sentry-project", () => {
7064 let buildProjectUrlSpy : ReturnType < typeof spyOn > ;
7165 let selectSpy : ReturnType < typeof spyOn > ;
7266 let isCancelSpy : ReturnType < typeof spyOn > ;
73- let getOrgByNumericIdSpy : ReturnType < typeof spyOn > ;
74- let detectDsnSpy : ReturnType < typeof spyOn > ;
75- let getCachedProjectByDsnKeySpy : ReturnType < typeof spyOn > ;
76- let setCachedProjectByDsnKeySpy : ReturnType < typeof spyOn > ;
77- let findProjectByDsnKeySpy : ReturnType < typeof spyOn > ;
78- let getProjectSpy : ReturnType < typeof spyOn > ;
7967
8068 beforeEach ( ( ) => {
8169 resolveOrgSpy = spyOn ( resolveTarget , "resolveOrg" ) ;
@@ -88,13 +76,6 @@ describe("create-sentry-project", () => {
8876 isCancelSpy = spyOn ( clack , "isCancel" ) . mockImplementation (
8977 ( v : unknown ) => v === Symbol . for ( "cancel" )
9078 ) ;
91- // New spies — default to no-op so existing tests are unaffected
92- getOrgByNumericIdSpy = spyOn ( dbRegions , "getOrgByNumericId" ) . mockResolvedValue ( undefined ) ;
93- detectDsnSpy = spyOn ( dsnIndex , "detectDsn" ) . mockResolvedValue ( null ) ;
94- getCachedProjectByDsnKeySpy = spyOn ( projectCache , "getCachedProjectByDsnKey" ) . mockResolvedValue ( undefined ) ;
95- setCachedProjectByDsnKeySpy = spyOn ( projectCache , "setCachedProjectByDsnKey" ) . mockResolvedValue ( undefined ) ;
96- findProjectByDsnKeySpy = spyOn ( apiClient , "findProjectByDsnKey" ) . mockResolvedValue ( null ) ;
97- getProjectSpy = spyOn ( apiClient , "getProject" ) ;
9879 } ) ;
9980
10081 afterEach ( ( ) => {
@@ -106,12 +87,6 @@ describe("create-sentry-project", () => {
10687 buildProjectUrlSpy . mockRestore ( ) ;
10788 selectSpy . mockRestore ( ) ;
10889 isCancelSpy . mockRestore ( ) ;
109- getOrgByNumericIdSpy . mockRestore ( ) ;
110- detectDsnSpy . mockRestore ( ) ;
111- getCachedProjectByDsnKeySpy . mockRestore ( ) ;
112- setCachedProjectByDsnKeySpy . mockRestore ( ) ;
113- findProjectByDsnKeySpy . mockRestore ( ) ;
114- getProjectSpy . mockRestore ( ) ;
11590 } ) ;
11691
11792 function mockDownstreamSuccess ( orgSlug : string ) {
@@ -266,195 +241,4 @@ describe("create-sentry-project", () => {
266241 const data = result . data as { dsn : string } ;
267242 expect ( data . dsn ) . toBe ( "" ) ;
268243 } ) ;
269-
270- describe ( "resolveOrgSlug — numeric org ID from DSN" , ( ) => {
271- test ( "numeric ID + cache hit → resolved to slug for project creation" , async ( ) => {
272- resolveOrgSpy . mockResolvedValue ( { org : "4507492088676352" } ) ;
273- getOrgByNumericIdSpy . mockResolvedValue ( {
274- slug : "acme-corp" ,
275- regionUrl : "https://us.sentry.io" ,
276- } ) ;
277- mockDownstreamSuccess ( "acme-corp" ) ;
278-
279- const result = await handleLocalOp ( makePayload ( ) , makeOptions ( ) ) ;
280-
281- expect ( result . ok ) . toBe ( true ) ;
282- const data = result . data as { orgSlug : string } ;
283- expect ( data . orgSlug ) . toBe ( "acme-corp" ) ;
284- expect ( getOrgByNumericIdSpy ) . toHaveBeenCalledWith ( "4507492088676352" ) ;
285- } ) ;
286-
287- test ( "numeric ID + cache miss → falls through to single org in listOrganizations" , async ( ) => {
288- resolveOrgSpy . mockResolvedValue ( { org : "4507492088676352" } ) ;
289- getOrgByNumericIdSpy . mockResolvedValue ( undefined ) ;
290- listOrgsSpy . mockResolvedValue ( [
291- { id : "1" , slug : "solo-org" , name : "Solo Org" } ,
292- ] ) ;
293- mockDownstreamSuccess ( "solo-org" ) ;
294-
295- const result = await handleLocalOp ( makePayload ( ) , makeOptions ( ) ) ;
296-
297- expect ( result . ok ) . toBe ( true ) ;
298- const data = result . data as { orgSlug : string } ;
299- expect ( data . orgSlug ) . toBe ( "solo-org" ) ;
300- } ) ;
301-
302- test ( "numeric ID + cache miss + multiple orgs + --yes → error with org list" , async ( ) => {
303- resolveOrgSpy . mockResolvedValue ( { org : "4507492088676352" } ) ;
304- getOrgByNumericIdSpy . mockResolvedValue ( undefined ) ;
305- listOrgsSpy . mockResolvedValue ( [
306- { id : "1" , slug : "org-a" , name : "Org A" } ,
307- { id : "2" , slug : "org-b" , name : "Org B" } ,
308- ] ) ;
309-
310- const result = await handleLocalOp (
311- makePayload ( ) ,
312- makeOptions ( { yes : true } )
313- ) ;
314-
315- expect ( result . ok ) . toBe ( false ) ;
316- expect ( result . error ) . toContain ( "Multiple organizations found" ) ;
317- expect ( createProjectSpy ) . not . toHaveBeenCalled ( ) ;
318- } ) ;
319- } ) ;
320-
321- describe ( "detectExistingProject — existing DSN prompt" , ( ) => {
322- function mockExistingProject ( orgSlug : string , projectSlug : string ) {
323- detectDsnSpy . mockResolvedValue ( {
324- publicKey : "test-key-abc" ,
325- protocol : "https" ,
326- host : `o123.ingest.sentry.io` ,
327- projectId : "42" ,
328- raw : `https://test-key-abc@o123.ingest.sentry.io/42` ,
329- source : "env_file" as const ,
330- } ) ;
331- getCachedProjectByDsnKeySpy . mockResolvedValue ( {
332- orgSlug,
333- orgName : orgSlug ,
334- projectSlug,
335- projectName : projectSlug ,
336- projectId : "42" ,
337- cachedAt : Date . now ( ) ,
338- } ) ;
339- getProjectSpy . mockResolvedValue ( { ...sampleProject , slug : projectSlug } ) ;
340- tryGetPrimaryDsnSpy . mockResolvedValue ( "https://abc@o1.ingest.sentry.io/42" ) ;
341- buildProjectUrlSpy . mockReturnValue (
342- `https://sentry.io/settings/${ orgSlug } /projects/${ projectSlug } /`
343- ) ;
344- }
345-
346- test ( "no DSN found → no prompt, proceeds with normal creation" , async ( ) => {
347- detectDsnSpy . mockResolvedValue ( null ) ;
348- resolveOrgSpy . mockResolvedValue ( { org : "acme-corp" } ) ;
349- mockDownstreamSuccess ( "acme-corp" ) ;
350-
351- const result = await handleLocalOp ( makePayload ( ) , makeOptions ( ) ) ;
352-
353- expect ( result . ok ) . toBe ( true ) ;
354- expect ( selectSpy ) . not . toHaveBeenCalled ( ) ;
355- expect ( createProjectSpy ) . toHaveBeenCalledTimes ( 1 ) ;
356- } ) ;
357-
358- test ( "DSN found + --yes flag → auto-uses existing project without prompt" , async ( ) => {
359- mockExistingProject ( "acme-corp" , "my-app" ) ;
360-
361- const result = await handleLocalOp (
362- makePayload ( ) ,
363- makeOptions ( { yes : true } )
364- ) ;
365-
366- expect ( result . ok ) . toBe ( true ) ;
367- const data = result . data as { orgSlug : string ; projectSlug : string } ;
368- expect ( data . orgSlug ) . toBe ( "acme-corp" ) ;
369- expect ( data . projectSlug ) . toBe ( "my-app" ) ;
370- expect ( selectSpy ) . not . toHaveBeenCalled ( ) ;
371- expect ( createProjectSpy ) . not . toHaveBeenCalled ( ) ;
372- } ) ;
373-
374- test ( "DSN found + pick 'existing' → returns existing project details" , async ( ) => {
375- mockExistingProject ( "acme-corp" , "my-app" ) ;
376- selectSpy . mockResolvedValue ( "existing" ) ;
377-
378- const result = await handleLocalOp ( makePayload ( ) , makeOptions ( ) ) ;
379-
380- expect ( result . ok ) . toBe ( true ) ;
381- const data = result . data as { orgSlug : string ; projectSlug : string } ;
382- expect ( data . orgSlug ) . toBe ( "acme-corp" ) ;
383- expect ( data . projectSlug ) . toBe ( "my-app" ) ;
384- expect ( createProjectSpy ) . not . toHaveBeenCalled ( ) ;
385- } ) ;
386-
387- test ( "DSN found + pick 'create' → proceeds with normal project creation" , async ( ) => {
388- mockExistingProject ( "acme-corp" , "my-app" ) ;
389- selectSpy . mockResolvedValue ( "create" ) ;
390- resolveOrgSpy . mockResolvedValue ( { org : "acme-corp" } ) ;
391- mockDownstreamSuccess ( "acme-corp" ) ;
392-
393- const result = await handleLocalOp ( makePayload ( ) , makeOptions ( ) ) ;
394-
395- expect ( result . ok ) . toBe ( true ) ;
396- expect ( createProjectSpy ) . toHaveBeenCalledTimes ( 1 ) ;
397- } ) ;
398-
399- test ( "DSN found + cancel select → ok:false with cancelled error" , async ( ) => {
400- mockExistingProject ( "acme-corp" , "my-app" ) ;
401- selectSpy . mockResolvedValue ( Symbol . for ( "cancel" ) ) ;
402-
403- const result = await handleLocalOp ( makePayload ( ) , makeOptions ( ) ) ;
404-
405- expect ( result . ok ) . toBe ( false ) ;
406- expect ( result . error ) . toContain ( "Cancelled" ) ;
407- expect ( createProjectSpy ) . not . toHaveBeenCalled ( ) ;
408- } ) ;
409-
410- test ( "DSN found + API lookup (cache miss) → caches project and prompts user" , async ( ) => {
411- detectDsnSpy . mockResolvedValue ( {
412- publicKey : "test-key-abc" ,
413- protocol : "https" ,
414- host : "o123.ingest.sentry.io" ,
415- projectId : "42" ,
416- raw : "https://test-key-abc@o123.ingest.sentry.io/42" ,
417- source : "env_file" as const ,
418- } ) ;
419- getCachedProjectByDsnKeySpy . mockResolvedValue ( undefined ) ; // cache miss
420- findProjectByDsnKeySpy . mockResolvedValue ( {
421- ...sampleProject ,
422- organization : { id : "1" , slug : "acme-corp" , name : "Acme Corp" } ,
423- } ) ;
424- setCachedProjectByDsnKeySpy . mockResolvedValue ( undefined ) ;
425- selectSpy . mockResolvedValue ( "existing" ) ;
426- getProjectSpy . mockResolvedValue ( sampleProject ) ;
427- tryGetPrimaryDsnSpy . mockResolvedValue ( "https://abc@o1.ingest.sentry.io/42" ) ;
428- buildProjectUrlSpy . mockReturnValue (
429- "https://sentry.io/settings/acme-corp/projects/my-app/"
430- ) ;
431-
432- const result = await handleLocalOp ( makePayload ( ) , makeOptions ( ) ) ;
433-
434- expect ( result . ok ) . toBe ( true ) ;
435- expect ( setCachedProjectByDsnKeySpy ) . toHaveBeenCalledTimes ( 1 ) ;
436- expect ( createProjectSpy ) . not . toHaveBeenCalled ( ) ;
437- } ) ;
438-
439- test ( "DSN found + API throws (inaccessible org) → no prompt, normal creation" , async ( ) => {
440- detectDsnSpy . mockResolvedValue ( {
441- publicKey : "test-key-abc" ,
442- protocol : "https" ,
443- host : "o999.ingest.sentry.io" ,
444- projectId : "99" ,
445- raw : "https://test-key-abc@o999.ingest.sentry.io/99" ,
446- source : "env_file" as const ,
447- } ) ;
448- getCachedProjectByDsnKeySpy . mockResolvedValue ( undefined ) ;
449- findProjectByDsnKeySpy . mockRejectedValue ( new Error ( "403 Forbidden" ) ) ;
450- resolveOrgSpy . mockResolvedValue ( { org : "acme-corp" } ) ;
451- mockDownstreamSuccess ( "acme-corp" ) ;
452-
453- const result = await handleLocalOp ( makePayload ( ) , makeOptions ( ) ) ;
454-
455- expect ( result . ok ) . toBe ( true ) ;
456- expect ( selectSpy ) . not . toHaveBeenCalled ( ) ;
457- expect ( createProjectSpy ) . toHaveBeenCalledTimes ( 1 ) ;
458- } ) ;
459- } ) ;
460244} ) ;
0 commit comments