@@ -8,7 +8,8 @@ import { runCreateNamespace, runListArchitectures, runListNamespaces,
88 runCreateDomain , runListDomains , runListControls ,
99 runPushControlRequirement , runPullControlRequirement , runPushControlConfiguration , runPullControlConfiguration ,
1010 printIdCreateResult ,
11- runListControlConfigurations } from './hub-commands' ;
11+ runListControlConfigurations ,
12+ runBumpArchitecture , runBumpPattern , runBumpStandard , runBumpControlRequirement , runBumpControlConfiguration } from './hub-commands' ;
1213
1314// We stub the @finos /calm-shared HTTP client so no real HTTP is made, but keep the
1415// real (pure) document-id-utils helpers that orchestratePush relies on.
@@ -1416,5 +1417,218 @@ describe('hub-commands', () => {
14161417 } ) ;
14171418 } ) ;
14181419
1420+ // ── runBumpArchitecture ────────────────────────────────────────────────────
1421+
1422+ describe ( 'runBumpArchitecture' , ( ) => {
1423+ it ( 'bumps the version on disk when the current version already exists on the hub' , async ( ) => {
1424+ const { mockClient } = await getSharedMocks ( ) ;
1425+ vi . mocked ( mockClient . getMappedResourceVersions ) . mockResolvedValue ( [ '1.0.0' ] ) ;
1426+
1427+ await runBumpArchitecture ( { calmHubOptions : { calmHubUrl : 'http://hub' } , file : 'arch.json' , changeType : 'PATCH' } ) ;
1428+
1429+ expect ( mockClient . getMappedResourceVersions ) . toHaveBeenCalledWith ( 'finos' , 'my-arch' , 'architectures' ) ;
1430+ expect ( fs . writeFile ) . toHaveBeenCalledWith ( 'arch.json' , expect . stringContaining ( '/versions/1.0.1' ) , 'utf-8' ) ;
1431+ expect ( hubOutput . printJsonSuccess ) . toHaveBeenCalledWith (
1432+ expect . objectContaining ( { file : 'arch.json' , previousVersion : '1.0.0' , newVersion : '1.0.1' } )
1433+ ) ;
1434+ } ) ;
1435+
1436+ it ( 'bumps by minor when changeType is MINOR' , async ( ) => {
1437+ const { mockClient } = await getSharedMocks ( ) ;
1438+ vi . mocked ( mockClient . getMappedResourceVersions ) . mockResolvedValue ( [ '1.0.0' ] ) ;
1439+
1440+ await runBumpArchitecture ( { calmHubOptions : { calmHubUrl : 'http://hub' } , file : 'arch.json' , changeType : 'MINOR' } ) ;
1441+
1442+ expect ( fs . writeFile ) . toHaveBeenCalledWith ( 'arch.json' , expect . stringContaining ( '/versions/1.1.0' ) , 'utf-8' ) ;
1443+ } ) ;
1444+
1445+ it ( 'bumps by major when changeType is MAJOR' , async ( ) => {
1446+ const { mockClient } = await getSharedMocks ( ) ;
1447+ vi . mocked ( mockClient . getMappedResourceVersions ) . mockResolvedValue ( [ '1.0.0' ] ) ;
1448+
1449+ await runBumpArchitecture ( { calmHubOptions : { calmHubUrl : 'http://hub' } , file : 'arch.json' , changeType : 'MAJOR' } ) ;
1450+
1451+ expect ( fs . writeFile ) . toHaveBeenCalledWith ( 'arch.json' , expect . stringContaining ( '/versions/2.0.0' ) , 'utf-8' ) ;
1452+ } ) ;
1453+
1454+ it ( 'does not write the file and prints a no-op message when the version is not yet published' , async ( ) => {
1455+ const { mockClient } = await getSharedMocks ( ) ;
1456+ vi . mocked ( mockClient . getMappedResourceVersions ) . mockResolvedValue ( [ ] ) ;
1457+
1458+ await runBumpArchitecture ( { calmHubOptions : { calmHubUrl : 'http://hub' } , file : 'arch.json' , changeType : 'PATCH' } ) ;
1459+
1460+ expect ( fs . writeFile ) . not . toHaveBeenCalled ( ) ;
1461+ expect ( hubOutput . printJsonSuccess ) . toHaveBeenCalledWith (
1462+ expect . objectContaining ( { bumped : false } )
1463+ ) ;
1464+ } ) ;
1465+
1466+ it ( 'does not write the file when the exact on-disk version is not in the hub versions list' , async ( ) => {
1467+ const { mockClient } = await getSharedMocks ( ) ;
1468+ // Hub has 2.0.0 but file has 1.0.0 — version mismatch, no bump
1469+ vi . mocked ( mockClient . getMappedResourceVersions ) . mockResolvedValue ( [ '2.0.0' ] ) ;
1470+
1471+ await runBumpArchitecture ( { calmHubOptions : { calmHubUrl : 'http://hub' } , file : 'arch.json' , changeType : 'PATCH' } ) ;
1472+
1473+ expect ( fs . writeFile ) . not . toHaveBeenCalled ( ) ;
1474+ expect ( hubOutput . printJsonSuccess ) . toHaveBeenCalledWith ( expect . objectContaining ( { bumped : false } ) ) ;
1475+ } ) ;
1476+
1477+ it ( 'does not push to the hub' , async ( ) => {
1478+ const { mockClient } = await getSharedMocks ( ) ;
1479+ vi . mocked ( mockClient . getMappedResourceVersions ) . mockResolvedValue ( [ '1.0.0' ] ) ;
1480+
1481+ await runBumpArchitecture ( { calmHubOptions : { calmHubUrl : 'http://hub' } , file : 'arch.json' , changeType : 'PATCH' } ) ;
1482+
1483+ expect ( mockClient . createMappedResourceVersion ) . not . toHaveBeenCalled ( ) ;
1484+ } ) ;
1485+
1486+ it ( 'renders a pretty table when format is pretty' , async ( ) => {
1487+ const { mockClient } = await getSharedMocks ( ) ;
1488+ vi . mocked ( mockClient . getMappedResourceVersions ) . mockResolvedValue ( [ '1.0.0' ] ) ;
1489+
1490+ await runBumpArchitecture ( { calmHubOptions : { calmHubUrl : 'http://hub' } , file : 'arch.json' , changeType : 'PATCH' , format : 'pretty' } ) ;
1491+
1492+ expect ( hubOutput . printTableSuccess ) . toHaveBeenCalledWith (
1493+ [ { FILE : 'arch.json' , FROM : '1.0.0' , TO : '1.0.1' } ] ,
1494+ expect . arrayContaining ( [ expect . objectContaining ( { key : 'FROM' } ) , expect . objectContaining ( { key : 'TO' } ) ] )
1495+ ) ;
1496+ } ) ;
1497+
1498+ it ( 'exits on HubClientError' , async ( ) => {
1499+ const { mockClient, shared } = await getSharedMocks ( ) ;
1500+ vi . mocked ( mockClient . getMappedResourceVersions ) . mockRejectedValue (
1501+ new shared . HubClientError ( 503 , 'Service unavailable' , 'GET /calm/namespaces/finos/architectures/my-arch/versions' )
1502+ ) ;
1503+
1504+ await expect ( runBumpArchitecture ( { calmHubOptions : { calmHubUrl : 'http://hub' } , file : 'arch.json' , changeType : 'PATCH' } ) )
1505+ . rejects . toThrow ( 'process.exit' ) ;
1506+ expect ( hubOutput . printError ) . toHaveBeenCalledWith ( 503 , 'Service unavailable' , expect . any ( String ) , 'json' ) ;
1507+ } ) ;
1508+ } ) ;
1509+
1510+ describe ( 'runBumpPattern' , ( ) => {
1511+ it ( 'bumps the version on disk when the current version already exists on the hub' , async ( ) => {
1512+ const { mockClient } = await getSharedMocks ( ) ;
1513+ vi . mocked ( fs . readFile ) . mockResolvedValue ( pushDoc ( 'my-pattern' , 'patterns' ) as unknown as Uint8Array ) ;
1514+ vi . mocked ( mockClient . getMappedResourceVersions ) . mockResolvedValue ( [ '1.0.0' ] ) ;
1515+
1516+ await runBumpPattern ( { calmHubOptions : { calmHubUrl : 'http://hub' } , file : 'pattern.json' , changeType : 'PATCH' } ) ;
1517+
1518+ expect ( mockClient . getMappedResourceVersions ) . toHaveBeenCalledWith ( 'finos' , 'my-pattern' , 'patterns' ) ;
1519+ expect ( fs . writeFile ) . toHaveBeenCalledWith ( 'pattern.json' , expect . stringContaining ( '/versions/1.0.1' ) , 'utf-8' ) ;
1520+ } ) ;
1521+ } ) ;
1522+
1523+ describe ( 'runBumpStandard' , ( ) => {
1524+ it ( 'bumps the version on disk when the current version already exists on the hub' , async ( ) => {
1525+ const { mockClient } = await getSharedMocks ( ) ;
1526+ vi . mocked ( fs . readFile ) . mockResolvedValue ( pushDoc ( 'my-standard' , 'standards' ) as unknown as Uint8Array ) ;
1527+ vi . mocked ( mockClient . getMappedResourceVersions ) . mockResolvedValue ( [ '1.0.0' ] ) ;
1528+
1529+ await runBumpStandard ( { calmHubOptions : { calmHubUrl : 'http://hub' } , file : 'standard.json' , changeType : 'PATCH' } ) ;
1530+
1531+ expect ( mockClient . getMappedResourceVersions ) . toHaveBeenCalledWith ( 'finos' , 'my-standard' , 'standards' ) ;
1532+ expect ( fs . writeFile ) . toHaveBeenCalledWith ( 'standard.json' , expect . stringContaining ( '/versions/1.0.1' ) , 'utf-8' ) ;
1533+ } ) ;
1534+ } ) ;
1535+
1536+ // ── runBumpControlRequirement ──────────────────────────────────────────────
1537+
1538+ describe ( 'runBumpControlRequirement' , ( ) => {
1539+ it ( 'bumps the version on disk when the current version already exists on the hub' , async ( ) => {
1540+ const { mockClient } = await getSharedMocks ( ) ;
1541+ vi . mocked ( fs . readFile ) . mockResolvedValue ( controlReqDoc ( ) as unknown as Uint8Array ) ;
1542+ vi . mocked ( mockClient . getControlRequirementVersions ) . mockResolvedValue ( [ '1.0.0' ] ) ;
1543+
1544+ await runBumpControlRequirement ( { calmHubOptions : { calmHubUrl : 'http://hub' } , file : 'req.json' , changeType : 'PATCH' } ) ;
1545+
1546+ expect ( mockClient . getControlRequirementVersions ) . toHaveBeenCalledWith ( 'security' , 'access-control' ) ;
1547+ expect ( fs . writeFile ) . toHaveBeenCalledWith ( 'req.json' , expect . stringContaining ( '/requirement/versions/1.0.1' ) , 'utf-8' ) ;
1548+ expect ( hubOutput . printJsonSuccess ) . toHaveBeenCalledWith (
1549+ expect . objectContaining ( { file : 'req.json' , previousVersion : '1.0.0' , newVersion : '1.0.1' } )
1550+ ) ;
1551+ } ) ;
1552+
1553+ it ( 'does not write the file when the current version is not yet published' , async ( ) => {
1554+ const { mockClient } = await getSharedMocks ( ) ;
1555+ vi . mocked ( fs . readFile ) . mockResolvedValue ( controlReqDoc ( ) as unknown as Uint8Array ) ;
1556+ vi . mocked ( mockClient . getControlRequirementVersions ) . mockResolvedValue ( [ ] ) ;
1557+
1558+ await runBumpControlRequirement ( { calmHubOptions : { calmHubUrl : 'http://hub' } , file : 'req.json' , changeType : 'PATCH' } ) ;
1559+
1560+ expect ( fs . writeFile ) . not . toHaveBeenCalled ( ) ;
1561+ expect ( hubOutput . printJsonSuccess ) . toHaveBeenCalledWith ( expect . objectContaining ( { bumped : false } ) ) ;
1562+ } ) ;
1563+
1564+ it ( 'does not push to the hub' , async ( ) => {
1565+ const { mockClient } = await getSharedMocks ( ) ;
1566+ vi . mocked ( fs . readFile ) . mockResolvedValue ( controlReqDoc ( ) as unknown as Uint8Array ) ;
1567+ vi . mocked ( mockClient . getControlRequirementVersions ) . mockResolvedValue ( [ '1.0.0' ] ) ;
1568+
1569+ await runBumpControlRequirement ( { calmHubOptions : { calmHubUrl : 'http://hub' } , file : 'req.json' , changeType : 'PATCH' } ) ;
1570+
1571+ expect ( mockClient . createControlRequirementVersion ) . not . toHaveBeenCalled ( ) ;
1572+ } ) ;
1573+
1574+ it ( 'exits when the document $id describes a configuration, not a requirement' , async ( ) => {
1575+ vi . mocked ( fs . readFile ) . mockResolvedValue ( controlConfigDoc ( ) as unknown as Uint8Array ) ;
1576+
1577+ await expect ( runBumpControlRequirement ( { calmHubOptions : { calmHubUrl : 'http://hub' } , file : 'req.json' , changeType : 'PATCH' } ) )
1578+ . rejects . toThrow ( 'process.exit' ) ;
1579+ expect ( hubOutput . printError ) . toHaveBeenCalledWith (
1580+ 0 , expect . stringContaining ( 'describes a control configuration, but a control requirement was expected' ) , expect . any ( String ) , 'json'
1581+ ) ;
1582+ } ) ;
1583+ } ) ;
1584+
1585+ // ── runBumpControlConfiguration ────────────────────────────────────────────
1586+
1587+ describe ( 'runBumpControlConfiguration' , ( ) => {
1588+ it ( 'bumps the version on disk when the current version already exists on the hub' , async ( ) => {
1589+ const { mockClient } = await getSharedMocks ( ) ;
1590+ vi . mocked ( fs . readFile ) . mockResolvedValue ( controlConfigDoc ( ) as unknown as Uint8Array ) ;
1591+ vi . mocked ( mockClient . getControlConfigurationVersions ) . mockResolvedValue ( [ '1.0.0' ] ) ;
1592+
1593+ await runBumpControlConfiguration ( { calmHubOptions : { calmHubUrl : 'http://hub' } , file : 'config.json' , changeType : 'PATCH' } ) ;
1594+
1595+ expect ( mockClient . getControlConfigurationVersions ) . toHaveBeenCalledWith ( 'security' , 'access-control' , 'prod' ) ;
1596+ expect ( fs . writeFile ) . toHaveBeenCalledWith ( 'config.json' , expect . stringContaining ( '/configurations/prod/versions/1.0.1' ) , 'utf-8' ) ;
1597+ expect ( hubOutput . printJsonSuccess ) . toHaveBeenCalledWith (
1598+ expect . objectContaining ( { file : 'config.json' , previousVersion : '1.0.0' , newVersion : '1.0.1' } )
1599+ ) ;
1600+ } ) ;
1601+
1602+ it ( 'does not write the file when the current version is not yet published' , async ( ) => {
1603+ const { mockClient } = await getSharedMocks ( ) ;
1604+ vi . mocked ( fs . readFile ) . mockResolvedValue ( controlConfigDoc ( ) as unknown as Uint8Array ) ;
1605+ vi . mocked ( mockClient . getControlConfigurationVersions ) . mockResolvedValue ( [ ] ) ;
1606+
1607+ await runBumpControlConfiguration ( { calmHubOptions : { calmHubUrl : 'http://hub' } , file : 'config.json' , changeType : 'PATCH' } ) ;
1608+
1609+ expect ( fs . writeFile ) . not . toHaveBeenCalled ( ) ;
1610+ expect ( hubOutput . printJsonSuccess ) . toHaveBeenCalledWith ( expect . objectContaining ( { bumped : false } ) ) ;
1611+ } ) ;
1612+
1613+ it ( 'does not push to the hub' , async ( ) => {
1614+ const { mockClient } = await getSharedMocks ( ) ;
1615+ vi . mocked ( fs . readFile ) . mockResolvedValue ( controlConfigDoc ( ) as unknown as Uint8Array ) ;
1616+ vi . mocked ( mockClient . getControlConfigurationVersions ) . mockResolvedValue ( [ '1.0.0' ] ) ;
1617+
1618+ await runBumpControlConfiguration ( { calmHubOptions : { calmHubUrl : 'http://hub' } , file : 'config.json' , changeType : 'PATCH' } ) ;
1619+
1620+ expect ( mockClient . createControlConfigurationVersion ) . not . toHaveBeenCalled ( ) ;
1621+ } ) ;
1622+
1623+ it ( 'exits when the document $id describes a requirement, not a configuration' , async ( ) => {
1624+ vi . mocked ( fs . readFile ) . mockResolvedValue ( controlReqDoc ( ) as unknown as Uint8Array ) ;
1625+
1626+ await expect ( runBumpControlConfiguration ( { calmHubOptions : { calmHubUrl : 'http://hub' } , file : 'config.json' , changeType : 'PATCH' } ) )
1627+ . rejects . toThrow ( 'process.exit' ) ;
1628+ expect ( hubOutput . printError ) . toHaveBeenCalledWith (
1629+ 0 , expect . stringContaining ( 'describes a control requirement, but a control configuration was expected' ) , expect . any ( String ) , 'json'
1630+ ) ;
1631+ } ) ;
1632+ } ) ;
14191633
14201634} ) ;
0 commit comments