@@ -12,6 +12,7 @@ import {
1212 within ,
1313} from 'sentry-test/reactTestingLibrary' ;
1414
15+ import * as indicators from 'sentry/actionCreators/indicator' ;
1516import type { SeerPreferencesResponse } from 'sentry/components/events/autofix/preferences/hooks/useProjectSeerPreferences' ;
1617import type { Organization } from 'sentry/types/organization' ;
1718import type { Project } from 'sentry/types/project' ;
@@ -1224,4 +1225,385 @@ describe('ProjectSeer', () => {
12241225 ) . not . toBeInTheDocument ( ) ;
12251226 } ) ;
12261227 } ) ;
1228+
1229+ describe ( 'Auto-open PR and Cursor Handoff toggles with triage-signals-v0' , ( ) => {
1230+ it ( 'shows Auto-open PR toggle when Auto-Trigger is ON' , async ( ) => {
1231+ render ( < ProjectSeer /> , {
1232+ organization,
1233+ outletContext : {
1234+ project : ProjectFixture ( {
1235+ features : [ 'triage-signals-v0' ] ,
1236+ autofixAutomationTuning : 'medium' ,
1237+ } ) ,
1238+ } ,
1239+ } ) ;
1240+
1241+ await screen . findByText ( / A u t o m a t i o n / i) ;
1242+ expect ( screen . getByRole ( 'checkbox' , { name : / A u t o - o p e n P R / i} ) ) . toBeInTheDocument ( ) ;
1243+ } ) ;
1244+
1245+ it ( 'hides Auto-open PR toggle when Auto-Trigger is OFF' , async ( ) => {
1246+ render ( < ProjectSeer /> , {
1247+ organization,
1248+ outletContext : {
1249+ project : ProjectFixture ( {
1250+ features : [ 'triage-signals-v0' ] ,
1251+ autofixAutomationTuning : 'off' ,
1252+ } ) ,
1253+ } ,
1254+ } ) ;
1255+
1256+ await screen . findByText ( / A u t o m a t i o n / i) ;
1257+ expect (
1258+ screen . queryByRole ( 'checkbox' , { name : / A u t o - o p e n P R / i} )
1259+ ) . not . toBeInTheDocument ( ) ;
1260+ } ) ;
1261+
1262+ it ( 'shows Cursor handoff toggle when Auto-Trigger is ON and Cursor integration exists' , async ( ) => {
1263+ const orgWithCursor = OrganizationFixture ( {
1264+ features : [ 'autofix-seer-preferences' , 'integrations-cursor' ] ,
1265+ } ) ;
1266+
1267+ MockApiClient . addMockResponse ( {
1268+ url : `/organizations/${ orgWithCursor . slug } /seer/setup-check/` ,
1269+ method : 'GET' ,
1270+ body : {
1271+ setupAcknowledgement : { orgHasAcknowledged : true , userHasAcknowledged : true } ,
1272+ billing : { hasAutofixQuota : true , hasScannerQuota : true } ,
1273+ } ,
1274+ } ) ;
1275+
1276+ MockApiClient . addMockResponse ( {
1277+ url : `/organizations/${ orgWithCursor . slug } /repos/` ,
1278+ query : { status : 'active' } ,
1279+ method : 'GET' ,
1280+ body : [ ] ,
1281+ } ) ;
1282+
1283+ MockApiClient . addMockResponse ( {
1284+ url : `/projects/${ orgWithCursor . slug } /${ project . slug } /seer/preferences/` ,
1285+ method : 'GET' ,
1286+ body : { code_mapping_repos : [ ] } ,
1287+ } ) ;
1288+
1289+ MockApiClient . addMockResponse ( {
1290+ url : `/organizations/${ orgWithCursor . slug } /integrations/coding-agents/` ,
1291+ method : 'GET' ,
1292+ body : {
1293+ integrations : [ { id : '123' , name : 'Cursor' , provider : 'cursor' } ] ,
1294+ } ,
1295+ } ) ;
1296+
1297+ render ( < ProjectSeer /> , {
1298+ organization : orgWithCursor ,
1299+ outletContext : {
1300+ project : ProjectFixture ( {
1301+ features : [ 'triage-signals-v0' ] ,
1302+ autofixAutomationTuning : 'medium' ,
1303+ } ) ,
1304+ } ,
1305+ } ) ;
1306+
1307+ await screen . findByText ( / A u t o m a t i o n / i) ;
1308+ expect (
1309+ screen . getByRole ( 'checkbox' , { name : / H a n d o f f t o C u r s o r / i} )
1310+ ) . toBeInTheDocument ( ) ;
1311+ } ) ;
1312+
1313+ it ( 'hides Cursor handoff toggle when no Cursor integration' , async ( ) => {
1314+ render ( < ProjectSeer /> , {
1315+ organization,
1316+ outletContext : {
1317+ project : ProjectFixture ( {
1318+ features : [ 'triage-signals-v0' ] ,
1319+ autofixAutomationTuning : 'medium' ,
1320+ } ) ,
1321+ } ,
1322+ } ) ;
1323+
1324+ await screen . findByText ( / A u t o m a t i o n / i) ;
1325+ expect (
1326+ screen . queryByRole ( 'checkbox' , { name : / H a n d o f f t o C u r s o r / i} )
1327+ ) . not . toBeInTheDocument ( ) ;
1328+ } ) ;
1329+
1330+ it ( 'updates preferences when Auto-open PR toggle is changed' , async ( ) => {
1331+ MockApiClient . addMockResponse ( {
1332+ url : `/projects/${ organization . slug } /${ project . slug } /` ,
1333+ method : 'PUT' ,
1334+ body : { } ,
1335+ } ) ;
1336+
1337+ const seerPreferencesPostRequest = MockApiClient . addMockResponse ( {
1338+ url : `/projects/${ organization . slug } /${ project . slug } /seer/preferences/` ,
1339+ method : 'POST' ,
1340+ } ) ;
1341+
1342+ render ( < ProjectSeer /> , {
1343+ organization,
1344+ outletContext : {
1345+ project : ProjectFixture ( {
1346+ features : [ 'triage-signals-v0' ] ,
1347+ autofixAutomationTuning : 'medium' ,
1348+ } ) ,
1349+ } ,
1350+ } ) ;
1351+
1352+ const toggle = await screen . findByRole ( 'checkbox' , { name : / A u t o - o p e n P R / i} ) ;
1353+ await userEvent . click ( toggle ) ;
1354+
1355+ await waitFor ( ( ) => {
1356+ expect ( seerPreferencesPostRequest ) . toHaveBeenCalledWith (
1357+ expect . anything ( ) ,
1358+ expect . objectContaining ( {
1359+ data : expect . objectContaining ( {
1360+ automated_run_stopping_point : 'open_pr' ,
1361+ automation_handoff : undefined ,
1362+ } ) ,
1363+ } )
1364+ ) ;
1365+ } ) ;
1366+ } ) ;
1367+
1368+ it ( 'updates preferences when Cursor handoff toggle is changed' , async ( ) => {
1369+ const orgWithCursor = OrganizationFixture ( {
1370+ features : [ 'autofix-seer-preferences' , 'integrations-cursor' ] ,
1371+ } ) ;
1372+
1373+ MockApiClient . addMockResponse ( {
1374+ url : `/organizations/${ orgWithCursor . slug } /seer/setup-check/` ,
1375+ method : 'GET' ,
1376+ body : {
1377+ setupAcknowledgement : { orgHasAcknowledged : true , userHasAcknowledged : true } ,
1378+ billing : { hasAutofixQuota : true , hasScannerQuota : true } ,
1379+ } ,
1380+ } ) ;
1381+
1382+ MockApiClient . addMockResponse ( {
1383+ url : `/organizations/${ orgWithCursor . slug } /repos/` ,
1384+ query : { status : 'active' } ,
1385+ method : 'GET' ,
1386+ body : [ ] ,
1387+ } ) ;
1388+
1389+ MockApiClient . addMockResponse ( {
1390+ url : `/projects/${ orgWithCursor . slug } /${ project . slug } /seer/preferences/` ,
1391+ method : 'GET' ,
1392+ body : { code_mapping_repos : [ ] } ,
1393+ } ) ;
1394+
1395+ MockApiClient . addMockResponse ( {
1396+ url : `/organizations/${ orgWithCursor . slug } /integrations/coding-agents/` ,
1397+ method : 'GET' ,
1398+ body : {
1399+ integrations : [ { id : '123' , name : 'Cursor' , provider : 'cursor' } ] ,
1400+ } ,
1401+ } ) ;
1402+
1403+ MockApiClient . addMockResponse ( {
1404+ url : `/projects/${ orgWithCursor . slug } /${ project . slug } /` ,
1405+ method : 'PUT' ,
1406+ body : { } ,
1407+ } ) ;
1408+
1409+ const seerPreferencesPostRequest = MockApiClient . addMockResponse ( {
1410+ url : `/projects/${ orgWithCursor . slug } /${ project . slug } /seer/preferences/` ,
1411+ method : 'POST' ,
1412+ } ) ;
1413+
1414+ render ( < ProjectSeer /> , {
1415+ organization : orgWithCursor ,
1416+ outletContext : {
1417+ project : ProjectFixture ( {
1418+ features : [ 'triage-signals-v0' ] ,
1419+ autofixAutomationTuning : 'medium' ,
1420+ } ) ,
1421+ } ,
1422+ } ) ;
1423+
1424+ const toggle = await screen . findByRole ( 'checkbox' , { name : / H a n d o f f t o C u r s o r / i} ) ;
1425+ await userEvent . click ( toggle ) ;
1426+
1427+ await waitFor ( ( ) => {
1428+ expect ( seerPreferencesPostRequest ) . toHaveBeenCalledWith (
1429+ expect . anything ( ) ,
1430+ expect . objectContaining ( {
1431+ data : expect . objectContaining ( {
1432+ automated_run_stopping_point : 'root_cause' ,
1433+ automation_handoff : {
1434+ handoff_point : 'root_cause' ,
1435+ target : 'cursor_background_agent' ,
1436+ integration_id : 123 ,
1437+ auto_create_pr : false ,
1438+ } ,
1439+ } ) ,
1440+ } )
1441+ ) ;
1442+ } ) ;
1443+ } ) ;
1444+
1445+ it ( 'shows error when Cursor handoff fails due to missing integration' , async ( ) => {
1446+ const orgWithCursor = OrganizationFixture ( {
1447+ features : [ 'autofix-seer-preferences' , 'integrations-cursor' ] ,
1448+ } ) ;
1449+
1450+ MockApiClient . addMockResponse ( {
1451+ url : `/organizations/${ orgWithCursor . slug } /seer/setup-check/` ,
1452+ method : 'GET' ,
1453+ body : {
1454+ setupAcknowledgement : { orgHasAcknowledged : true , userHasAcknowledged : true } ,
1455+ billing : { hasAutofixQuota : true , hasScannerQuota : true } ,
1456+ } ,
1457+ } ) ;
1458+
1459+ MockApiClient . addMockResponse ( {
1460+ url : `/organizations/${ orgWithCursor . slug } /repos/` ,
1461+ query : { status : 'active' } ,
1462+ method : 'GET' ,
1463+ body : [ ] ,
1464+ } ) ;
1465+
1466+ MockApiClient . addMockResponse ( {
1467+ url : `/projects/${ orgWithCursor . slug } /${ project . slug } /seer/preferences/` ,
1468+ method : 'GET' ,
1469+ body : { code_mapping_repos : [ ] } ,
1470+ } ) ;
1471+
1472+ // Mock integrations endpoint returning empty array (no Cursor integration)
1473+ MockApiClient . addMockResponse ( {
1474+ url : `/organizations/${ orgWithCursor . slug } /integrations/coding-agents/` ,
1475+ method : 'GET' ,
1476+ body : { integrations : [ ] } ,
1477+ } ) ;
1478+
1479+ render ( < ProjectSeer /> , {
1480+ organization : orgWithCursor ,
1481+ outletContext : {
1482+ project : ProjectFixture ( {
1483+ features : [ 'triage-signals-v0' ] ,
1484+ autofixAutomationTuning : 'medium' ,
1485+ } ) ,
1486+ } ,
1487+ } ) ;
1488+
1489+ await screen . findByText ( / A u t o m a t i o n / i) ;
1490+
1491+ // Toggle should not be visible when no Cursor integration exists
1492+ expect (
1493+ screen . queryByRole ( 'checkbox' , { name : / H a n d o f f t o C u r s o r / i} )
1494+ ) . not . toBeInTheDocument ( ) ;
1495+ } ) ;
1496+
1497+ it ( 'shows error message when Auto-open PR toggle fails' , async ( ) => {
1498+ jest . spyOn ( indicators , 'addErrorMessage' ) ;
1499+
1500+ MockApiClient . addMockResponse ( {
1501+ url : `/projects/${ organization . slug } /${ project . slug } /` ,
1502+ method : 'PUT' ,
1503+ body : { } ,
1504+ } ) ;
1505+
1506+ const seerPreferencesPostRequest = MockApiClient . addMockResponse ( {
1507+ url : `/projects/${ organization . slug } /${ project . slug } /seer/preferences/` ,
1508+ method : 'POST' ,
1509+ statusCode : 500 ,
1510+ body : { detail : 'Internal Server Error' } ,
1511+ } ) ;
1512+
1513+ render ( < ProjectSeer /> , {
1514+ organization,
1515+ outletContext : {
1516+ project : ProjectFixture ( {
1517+ features : [ 'triage-signals-v0' ] ,
1518+ autofixAutomationTuning : 'medium' ,
1519+ } ) ,
1520+ } ,
1521+ } ) ;
1522+
1523+ const toggle = await screen . findByRole ( 'checkbox' , { name : / A u t o - o p e n P R / i} ) ;
1524+ await userEvent . click ( toggle ) ;
1525+
1526+ await waitFor ( ( ) => {
1527+ expect ( seerPreferencesPostRequest ) . toHaveBeenCalled ( ) ;
1528+ } ) ;
1529+
1530+ // Should show error message
1531+ expect ( indicators . addErrorMessage ) . toHaveBeenCalledWith (
1532+ 'Failed to update auto-open PR setting'
1533+ ) ;
1534+ } ) ;
1535+
1536+ it ( 'shows error message when Cursor handoff toggle fails' , async ( ) => {
1537+ jest . spyOn ( indicators , 'addErrorMessage' ) ;
1538+
1539+ const orgWithCursor = OrganizationFixture ( {
1540+ features : [ 'autofix-seer-preferences' , 'integrations-cursor' ] ,
1541+ } ) ;
1542+
1543+ MockApiClient . addMockResponse ( {
1544+ url : `/organizations/${ orgWithCursor . slug } /seer/setup-check/` ,
1545+ method : 'GET' ,
1546+ body : {
1547+ setupAcknowledgement : { orgHasAcknowledged : true , userHasAcknowledged : true } ,
1548+ billing : { hasAutofixQuota : true , hasScannerQuota : true } ,
1549+ } ,
1550+ } ) ;
1551+
1552+ MockApiClient . addMockResponse ( {
1553+ url : `/organizations/${ orgWithCursor . slug } /repos/` ,
1554+ query : { status : 'active' } ,
1555+ method : 'GET' ,
1556+ body : [ ] ,
1557+ } ) ;
1558+
1559+ MockApiClient . addMockResponse ( {
1560+ url : `/projects/${ orgWithCursor . slug } /${ project . slug } /seer/preferences/` ,
1561+ method : 'GET' ,
1562+ body : { code_mapping_repos : [ ] } ,
1563+ } ) ;
1564+
1565+ MockApiClient . addMockResponse ( {
1566+ url : `/organizations/${ orgWithCursor . slug } /integrations/coding-agents/` ,
1567+ method : 'GET' ,
1568+ body : {
1569+ integrations : [ { id : '123' , name : 'Cursor' , provider : 'cursor' } ] ,
1570+ } ,
1571+ } ) ;
1572+
1573+ MockApiClient . addMockResponse ( {
1574+ url : `/projects/${ orgWithCursor . slug } /${ project . slug } /` ,
1575+ method : 'PUT' ,
1576+ body : { } ,
1577+ } ) ;
1578+
1579+ const seerPreferencesPostRequest = MockApiClient . addMockResponse ( {
1580+ url : `/projects/${ orgWithCursor . slug } /${ project . slug } /seer/preferences/` ,
1581+ method : 'POST' ,
1582+ statusCode : 500 ,
1583+ body : { detail : 'Internal Server Error' } ,
1584+ } ) ;
1585+
1586+ render ( < ProjectSeer /> , {
1587+ organization : orgWithCursor ,
1588+ outletContext : {
1589+ project : ProjectFixture ( {
1590+ features : [ 'triage-signals-v0' ] ,
1591+ autofixAutomationTuning : 'medium' ,
1592+ } ) ,
1593+ } ,
1594+ } ) ;
1595+
1596+ const toggle = await screen . findByRole ( 'checkbox' , { name : / H a n d o f f t o C u r s o r / i} ) ;
1597+ await userEvent . click ( toggle ) ;
1598+
1599+ await waitFor ( ( ) => {
1600+ expect ( seerPreferencesPostRequest ) . toHaveBeenCalled ( ) ;
1601+ } ) ;
1602+
1603+ // Should show error message
1604+ expect ( indicators . addErrorMessage ) . toHaveBeenCalledWith (
1605+ 'Failed to update Cursor handoff setting'
1606+ ) ;
1607+ } ) ;
1608+ } ) ;
12271609} ) ;
0 commit comments