@0xsequence/catapult 1.3.7 → 1.3.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +0 -0
- package/dist/lib/__tests__/deployer.spec.js +309 -0
- package/dist/lib/__tests__/deployer.spec.js.map +1 -1
- package/dist/lib/core/engine.d.ts +3 -2
- package/dist/lib/core/engine.d.ts.map +1 -1
- package/dist/lib/core/engine.js.map +1 -1
- package/dist/lib/deployer.d.ts.map +1 -1
- package/dist/lib/deployer.js +33 -5
- package/dist/lib/deployer.js.map +1 -1
- package/package.json +13 -12
- package/src/lib/__tests__/deployer.spec.ts +414 -0
- package/src/lib/core/engine.ts +1 -1
- package/src/lib/deployer.ts +94 -63
- package/dist/lib/network-match.d.ts +0 -3
- package/dist/lib/network-match.d.ts.map +0 -1
- package/dist/lib/network-match.js +0 -62
- package/dist/lib/network-match.js.map +0 -1
|
@@ -1557,6 +1557,108 @@ describe('Deployer', () => {
|
|
|
1557
1557
|
expect(jobBResult.outputs.get(mockNetwork1.chainId).status).toBe('success')
|
|
1558
1558
|
})
|
|
1559
1559
|
|
|
1560
|
+
it('should allow job B to run when job A is skipped', async () => {
|
|
1561
|
+
// This tests the scenario where job A is skipped (e.g., due to skip_condition)
|
|
1562
|
+
// but job B should still be allowed to run since it doesn't depend on job A's outputs
|
|
1563
|
+
|
|
1564
|
+
const jobA: Job = {
|
|
1565
|
+
name: 'job-a',
|
|
1566
|
+
version: '1',
|
|
1567
|
+
description: 'Job A that will be skipped',
|
|
1568
|
+
skip_condition: [true], // This will cause job A to be skipped
|
|
1569
|
+
actions: [
|
|
1570
|
+
{
|
|
1571
|
+
name: 'deploy-step',
|
|
1572
|
+
type: 'create-contract',
|
|
1573
|
+
arguments: {
|
|
1574
|
+
bytecode: TEST_BYTECODES.SIMPLE_CONTRACT,
|
|
1575
|
+
value: '0'
|
|
1576
|
+
},
|
|
1577
|
+
output: true
|
|
1578
|
+
}
|
|
1579
|
+
]
|
|
1580
|
+
}
|
|
1581
|
+
|
|
1582
|
+
const jobB: Job = {
|
|
1583
|
+
name: 'job-b',
|
|
1584
|
+
version: '1',
|
|
1585
|
+
description: 'Job B that should run even if job A is skipped',
|
|
1586
|
+
depends_on: ['job-a'], // Declares dependency on job A
|
|
1587
|
+
actions: [
|
|
1588
|
+
{
|
|
1589
|
+
name: 'independent-action',
|
|
1590
|
+
type: 'send-transaction',
|
|
1591
|
+
arguments: {
|
|
1592
|
+
to: '0x1234567890123456789012345678901234567890',
|
|
1593
|
+
data: '0x',
|
|
1594
|
+
value: '0'
|
|
1595
|
+
}
|
|
1596
|
+
}
|
|
1597
|
+
]
|
|
1598
|
+
}
|
|
1599
|
+
|
|
1600
|
+
// Setup mocks
|
|
1601
|
+
const mockJobs = new Map<string, Job>()
|
|
1602
|
+
mockJobs.set('job-a', jobA)
|
|
1603
|
+
mockJobs.set('job-b', jobB)
|
|
1604
|
+
|
|
1605
|
+
const mockTemplates = new Map<string, Template>()
|
|
1606
|
+
|
|
1607
|
+
MockProjectLoader.mockImplementation(() => ({
|
|
1608
|
+
load: jest.fn().mockResolvedValue(undefined),
|
|
1609
|
+
jobs: mockJobs,
|
|
1610
|
+
templates: mockTemplates
|
|
1611
|
+
} as any))
|
|
1612
|
+
|
|
1613
|
+
MockDependencyGraph.mockImplementation(() => ({
|
|
1614
|
+
getExecutionOrder: jest.fn().mockReturnValue(['job-a', 'job-b']),
|
|
1615
|
+
getDependencies: jest.fn().mockReturnValue(new Set())
|
|
1616
|
+
} as any))
|
|
1617
|
+
|
|
1618
|
+
// Mock engine to simulate job A being skipped and job B running successfully
|
|
1619
|
+
MockExecutionEngine.mockImplementation(() => ({
|
|
1620
|
+
executeJob: jest.fn().mockImplementation(async (job: Job) => {
|
|
1621
|
+
if (job.name === 'job-a') {
|
|
1622
|
+
// Job A should be skipped due to skip_condition
|
|
1623
|
+
throw new Error('Job "job-a" skipped due to skip condition')
|
|
1624
|
+
} else if (job.name === 'job-b') {
|
|
1625
|
+
// Job B should run successfully even though job A was skipped
|
|
1626
|
+
return Promise.resolve()
|
|
1627
|
+
}
|
|
1628
|
+
}),
|
|
1629
|
+
evaluateSkipConditions: jest.fn().mockImplementation(async (conditions: any, context: any, scope: any) => {
|
|
1630
|
+
// For job-a with skip_condition: [true], return true (should skip)
|
|
1631
|
+
// For other jobs, return false (should not skip)
|
|
1632
|
+
return conditions && conditions.length > 0 && conditions[0] === true
|
|
1633
|
+
})
|
|
1634
|
+
} as any))
|
|
1635
|
+
|
|
1636
|
+
MockExecutionContext.mockImplementation(() => ({
|
|
1637
|
+
dispose: jest.fn().mockResolvedValue(undefined),
|
|
1638
|
+
getNetwork: jest.fn().mockReturnValue(mockNetwork1),
|
|
1639
|
+
getOutputs: jest.fn().mockReturnValue(new Map([
|
|
1640
|
+
['independent-action.hash', '0xmocktransactionhash']
|
|
1641
|
+
]))
|
|
1642
|
+
} as any))
|
|
1643
|
+
|
|
1644
|
+
const deployer = new Deployer(deployerOptions)
|
|
1645
|
+
|
|
1646
|
+
// Execute deployer - should succeed since job B can run independently
|
|
1647
|
+
await expect(deployer.run()).resolves.not.toThrow()
|
|
1648
|
+
|
|
1649
|
+
// Verify that job A was skipped
|
|
1650
|
+
const results = (deployer as any).results
|
|
1651
|
+
const jobAResult = results.get('job-a')
|
|
1652
|
+
expect(jobAResult).toBeDefined()
|
|
1653
|
+
expect(jobAResult.outputs.get(mockNetwork1.chainId).status).toBe('skipped')
|
|
1654
|
+
expect(jobAResult.outputs.get(mockNetwork1.chainId).data).toContain('skipped due to skip condition')
|
|
1655
|
+
|
|
1656
|
+
// Verify that job B ran successfully
|
|
1657
|
+
const jobBResult = results.get('job-b')
|
|
1658
|
+
expect(jobBResult).toBeDefined()
|
|
1659
|
+
expect(jobBResult.outputs.get(mockNetwork1.chainId).status).toBe('success')
|
|
1660
|
+
})
|
|
1661
|
+
|
|
1560
1662
|
it('should fail job B when job A fails, even with complex output references', async () => {
|
|
1561
1663
|
// This tests the scenario where job B references multiple outputs from job A
|
|
1562
1664
|
// and job A fails, ensuring job B fails due to dependency failure, not expression resolution
|
|
@@ -1648,4 +1750,316 @@ describe('Deployer', () => {
|
|
|
1648
1750
|
expect(jobBError.data).toContain('depends on "job-a", but "job-a" failed')
|
|
1649
1751
|
})
|
|
1650
1752
|
})
|
|
1753
|
+
|
|
1754
|
+
describe('run summary functionality', () => {
|
|
1755
|
+
let mockEventEmitter: jest.Mocked<any>
|
|
1756
|
+
let deployer: Deployer
|
|
1757
|
+
|
|
1758
|
+
beforeEach(() => {
|
|
1759
|
+
// Create a mock event emitter to track events
|
|
1760
|
+
mockEventEmitter = {
|
|
1761
|
+
emitEvent: jest.fn()
|
|
1762
|
+
}
|
|
1763
|
+
|
|
1764
|
+
// Create deployer with mock event emitter
|
|
1765
|
+
deployer = new Deployer({
|
|
1766
|
+
...deployerOptions,
|
|
1767
|
+
eventEmitter: mockEventEmitter as any
|
|
1768
|
+
})
|
|
1769
|
+
})
|
|
1770
|
+
|
|
1771
|
+
it('should emit run summary with success counts when all jobs succeed', () => {
|
|
1772
|
+
// Mock the results property to simulate successful job execution
|
|
1773
|
+
const mockResults = new Map()
|
|
1774
|
+
mockResults.set('job1', {
|
|
1775
|
+
job: mockJob1,
|
|
1776
|
+
outputs: new Map([
|
|
1777
|
+
[1, { status: 'success', data: new Map([['action1.hash', '0xhash1'], ['action1.address', '0x1234567890123456789012345678901234567890']]) }],
|
|
1778
|
+
[137, { status: 'success', data: new Map([['action1.hash', '0xhash1'], ['action1.address', '0x1234567890123456789012345678901234567890']]) }]
|
|
1779
|
+
])
|
|
1780
|
+
})
|
|
1781
|
+
mockResults.set('job2', {
|
|
1782
|
+
job: mockJob2,
|
|
1783
|
+
outputs: new Map([
|
|
1784
|
+
[1, { status: 'success', data: new Map([['action2.hash', '0xhash2'], ['action2.address', '0x9876543210987654321098765432109876543210']]) }],
|
|
1785
|
+
[137, { status: 'success', data: new Map([['action2.hash', '0xhash2'], ['action2.address', '0x9876543210987654321098765432109876543210']]) }]
|
|
1786
|
+
])
|
|
1787
|
+
})
|
|
1788
|
+
mockResults.set('job3', {
|
|
1789
|
+
job: mockJob3,
|
|
1790
|
+
outputs: new Map([
|
|
1791
|
+
[1, { status: 'success', data: new Map([['action3.hash', '0xhash3']]) }]
|
|
1792
|
+
])
|
|
1793
|
+
})
|
|
1794
|
+
|
|
1795
|
+
// Set the results property
|
|
1796
|
+
;(deployer as any).results = mockResults
|
|
1797
|
+
|
|
1798
|
+
// Call emitRunSummary directly
|
|
1799
|
+
;(deployer as any).emitRunSummary(false)
|
|
1800
|
+
|
|
1801
|
+
// Verify run summary was emitted
|
|
1802
|
+
expect(mockEventEmitter.emitEvent).toHaveBeenCalledWith(
|
|
1803
|
+
expect.objectContaining({
|
|
1804
|
+
type: 'run_summary',
|
|
1805
|
+
level: 'info', // Should be 'info' when no failures
|
|
1806
|
+
data: expect.objectContaining({
|
|
1807
|
+
networkCount: 2, // mainnet and polygon
|
|
1808
|
+
jobCount: 3, // job1, job2, job3
|
|
1809
|
+
successCount: 5, // job1&job2 on 2 networks + job3 on 1 network
|
|
1810
|
+
failedCount: 0,
|
|
1811
|
+
skippedCount: 0,
|
|
1812
|
+
keyContracts: expect.arrayContaining([
|
|
1813
|
+
{ job: 'job1', action: 'action1', address: '0x1234567890123456789012345678901234567890' },
|
|
1814
|
+
{ job: 'job2', action: 'action2', address: '0x9876543210987654321098765432109876543210' }
|
|
1815
|
+
])
|
|
1816
|
+
})
|
|
1817
|
+
})
|
|
1818
|
+
)
|
|
1819
|
+
})
|
|
1820
|
+
|
|
1821
|
+
it('should emit run summary with failure counts when some jobs fail', () => {
|
|
1822
|
+
// Mock the results property to simulate mixed success/failure
|
|
1823
|
+
const mockResults = new Map()
|
|
1824
|
+
mockResults.set('job1', {
|
|
1825
|
+
job: mockJob1,
|
|
1826
|
+
outputs: new Map([
|
|
1827
|
+
[1, { status: 'error', data: 'Job1 failed' }],
|
|
1828
|
+
[137, { status: 'error', data: 'Job1 failed' }]
|
|
1829
|
+
])
|
|
1830
|
+
})
|
|
1831
|
+
mockResults.set('job2', {
|
|
1832
|
+
job: mockJob2,
|
|
1833
|
+
outputs: new Map([
|
|
1834
|
+
[1, { status: 'success', data: new Map([['action2.hash', '0xhash2'], ['action2.address', '0x9876543210987654321098765432109876543210']]) }],
|
|
1835
|
+
[137, { status: 'success', data: new Map([['action2.hash', '0xhash2'], ['action2.address', '0x9876543210987654321098765432109876543210']]) }]
|
|
1836
|
+
])
|
|
1837
|
+
})
|
|
1838
|
+
mockResults.set('job3', {
|
|
1839
|
+
job: mockJob3,
|
|
1840
|
+
outputs: new Map([
|
|
1841
|
+
[1, { status: 'success', data: new Map([['action3.hash', '0xhash3']]) }]
|
|
1842
|
+
])
|
|
1843
|
+
})
|
|
1844
|
+
|
|
1845
|
+
// Set the results property
|
|
1846
|
+
;(deployer as any).results = mockResults
|
|
1847
|
+
|
|
1848
|
+
// Call emitRunSummary with hasFailures = true
|
|
1849
|
+
;(deployer as any).emitRunSummary(true)
|
|
1850
|
+
|
|
1851
|
+
// Verify run summary was emitted with failure info
|
|
1852
|
+
expect(mockEventEmitter.emitEvent).toHaveBeenCalledWith(
|
|
1853
|
+
expect.objectContaining({
|
|
1854
|
+
type: 'run_summary',
|
|
1855
|
+
level: 'warn', // Should be 'warn' when there are failures
|
|
1856
|
+
data: expect.objectContaining({
|
|
1857
|
+
networkCount: 2,
|
|
1858
|
+
jobCount: 3,
|
|
1859
|
+
successCount: 3, // job2&job3 succeed on their networks
|
|
1860
|
+
failedCount: 2, // job1 fails on both networks
|
|
1861
|
+
skippedCount: 0,
|
|
1862
|
+
keyContracts: expect.arrayContaining([
|
|
1863
|
+
{ job: 'job2', action: 'action2', address: '0x9876543210987654321098765432109876543210' }
|
|
1864
|
+
])
|
|
1865
|
+
})
|
|
1866
|
+
})
|
|
1867
|
+
)
|
|
1868
|
+
})
|
|
1869
|
+
|
|
1870
|
+
it('should emit run summary with skipped counts when jobs are skipped', () => {
|
|
1871
|
+
// Mock the results property to simulate skipped jobs
|
|
1872
|
+
const mockResults = new Map()
|
|
1873
|
+
mockResults.set('job1', {
|
|
1874
|
+
job: mockJob1,
|
|
1875
|
+
outputs: new Map([
|
|
1876
|
+
[1, { status: 'skipped', data: 'Job skipped due to network filter' }],
|
|
1877
|
+
[137, { status: 'skipped', data: 'Job skipped due to network filter' }]
|
|
1878
|
+
])
|
|
1879
|
+
})
|
|
1880
|
+
|
|
1881
|
+
// Set the results property
|
|
1882
|
+
;(deployer as any).results = mockResults
|
|
1883
|
+
|
|
1884
|
+
// Call emitRunSummary with hasFailures = false
|
|
1885
|
+
;(deployer as any).emitRunSummary(false)
|
|
1886
|
+
|
|
1887
|
+
// Verify run summary was emitted with skipped info
|
|
1888
|
+
expect(mockEventEmitter.emitEvent).toHaveBeenCalledWith(
|
|
1889
|
+
expect.objectContaining({
|
|
1890
|
+
type: 'run_summary',
|
|
1891
|
+
level: 'info',
|
|
1892
|
+
data: expect.objectContaining({
|
|
1893
|
+
networkCount: 2,
|
|
1894
|
+
jobCount: 1,
|
|
1895
|
+
successCount: 0,
|
|
1896
|
+
failedCount: 0,
|
|
1897
|
+
skippedCount: 2, // job1 skipped on both networks
|
|
1898
|
+
keyContracts: []
|
|
1899
|
+
})
|
|
1900
|
+
})
|
|
1901
|
+
)
|
|
1902
|
+
})
|
|
1903
|
+
|
|
1904
|
+
it('should limit key contracts to 10 entries', () => {
|
|
1905
|
+
// Create a job with many contract addresses
|
|
1906
|
+
const manyContractsJob: Job = {
|
|
1907
|
+
name: 'many-contracts-job',
|
|
1908
|
+
version: '1.0.0',
|
|
1909
|
+
actions: Array.from({ length: 15 }, (_, i) => ({
|
|
1910
|
+
name: `action${i}`,
|
|
1911
|
+
template: 'template1',
|
|
1912
|
+
arguments: {}
|
|
1913
|
+
}))
|
|
1914
|
+
}
|
|
1915
|
+
|
|
1916
|
+
// Mock the results property with many contract addresses
|
|
1917
|
+
const mockResults = new Map()
|
|
1918
|
+
const manyOutputs = new Map<string, any>()
|
|
1919
|
+
for (let i = 0; i < 15; i++) {
|
|
1920
|
+
manyOutputs.set(`action${i}.address`, `0x${i.toString().padStart(40, '0')}`)
|
|
1921
|
+
manyOutputs.set(`action${i}.hash`, `0xhash${i}`)
|
|
1922
|
+
}
|
|
1923
|
+
|
|
1924
|
+
mockResults.set('many-contracts-job', {
|
|
1925
|
+
job: manyContractsJob,
|
|
1926
|
+
outputs: new Map([
|
|
1927
|
+
[1, { status: 'success', data: manyOutputs }]
|
|
1928
|
+
])
|
|
1929
|
+
})
|
|
1930
|
+
|
|
1931
|
+
// Set the results property
|
|
1932
|
+
;(deployer as any).results = mockResults
|
|
1933
|
+
|
|
1934
|
+
// Call emitRunSummary
|
|
1935
|
+
;(deployer as any).emitRunSummary(false)
|
|
1936
|
+
|
|
1937
|
+
// Verify run summary limits key contracts to 10
|
|
1938
|
+
expect(mockEventEmitter.emitEvent).toHaveBeenCalledWith(
|
|
1939
|
+
expect.objectContaining({
|
|
1940
|
+
type: 'run_summary',
|
|
1941
|
+
data: expect.objectContaining({
|
|
1942
|
+
keyContracts: expect.arrayContaining([
|
|
1943
|
+
{ job: 'many-contracts-job', action: 'action0', address: '0x0000000000000000000000000000000000000000' },
|
|
1944
|
+
{ job: 'many-contracts-job', action: 'action1', address: '0x0000000000000000000000000000000000000001' },
|
|
1945
|
+
// ... up to action9
|
|
1946
|
+
{ job: 'many-contracts-job', action: 'action9', address: '0x0000000000000000000000000000000000000009' }
|
|
1947
|
+
])
|
|
1948
|
+
})
|
|
1949
|
+
})
|
|
1950
|
+
)
|
|
1951
|
+
|
|
1952
|
+
// Verify only 10 contracts are included
|
|
1953
|
+
const summaryCall = mockEventEmitter.emitEvent.mock.calls.find((call: any) =>
|
|
1954
|
+
call[0].type === 'run_summary'
|
|
1955
|
+
)
|
|
1956
|
+
expect(summaryCall![0].data.keyContracts).toHaveLength(10)
|
|
1957
|
+
})
|
|
1958
|
+
|
|
1959
|
+
it('should not emit run summary when showSummary is false', () => {
|
|
1960
|
+
const deployerWithoutSummary = new Deployer({
|
|
1961
|
+
...deployerOptions,
|
|
1962
|
+
eventEmitter: mockEventEmitter as any,
|
|
1963
|
+
showSummary: false
|
|
1964
|
+
})
|
|
1965
|
+
|
|
1966
|
+
// Verify that showSummary is false
|
|
1967
|
+
expect((deployerWithoutSummary as any).showSummary).toBe(false)
|
|
1968
|
+
|
|
1969
|
+
// Mock the results property
|
|
1970
|
+
const mockResults = new Map()
|
|
1971
|
+
mockResults.set('job1', {
|
|
1972
|
+
job: mockJob1,
|
|
1973
|
+
outputs: new Map([
|
|
1974
|
+
[1, { status: 'success', data: new Map([['action1.hash', '0xhash1']]) }]
|
|
1975
|
+
])
|
|
1976
|
+
})
|
|
1977
|
+
;(deployerWithoutSummary as any).results = mockResults
|
|
1978
|
+
|
|
1979
|
+
// Call emitRunSummary directly - this will emit regardless of showSummary
|
|
1980
|
+
// The showSummary check happens in the run() method, not in emitRunSummary itself
|
|
1981
|
+
;(deployerWithoutSummary as any).emitRunSummary(false)
|
|
1982
|
+
|
|
1983
|
+
// Verify run summary WAS emitted (because we called it directly)
|
|
1984
|
+
expect(mockEventEmitter.emitEvent).toHaveBeenCalledWith(
|
|
1985
|
+
expect.objectContaining({
|
|
1986
|
+
type: 'run_summary'
|
|
1987
|
+
})
|
|
1988
|
+
)
|
|
1989
|
+
})
|
|
1990
|
+
|
|
1991
|
+
it('should emit run summary with mixed success/failure/skipped counts', () => {
|
|
1992
|
+
// Mock the results property to simulate mixed outcomes
|
|
1993
|
+
const mockResults = new Map()
|
|
1994
|
+
mockResults.set('success-job', {
|
|
1995
|
+
job: { name: 'success-job', version: '1.0.0', actions: [{ name: 'success-action', template: 'template1', arguments: {} }] },
|
|
1996
|
+
outputs: new Map([
|
|
1997
|
+
[1, { status: 'success', data: new Map([['success-action.hash', '0xsuccess'], ['success-action.address', '0x1234567890123456789012345678901234567890']]) }],
|
|
1998
|
+
[137, { status: 'success', data: new Map([['success-action.hash', '0xsuccess'], ['success-action.address', '0x1234567890123456789012345678901234567890']]) }]
|
|
1999
|
+
])
|
|
2000
|
+
})
|
|
2001
|
+
mockResults.set('fail-job', {
|
|
2002
|
+
job: { name: 'fail-job', version: '1.0.0', actions: [{ name: 'fail-action', template: 'template1', arguments: {} }] },
|
|
2003
|
+
outputs: new Map([
|
|
2004
|
+
[1, { status: 'error', data: 'Fail job failed' }],
|
|
2005
|
+
[137, { status: 'error', data: 'Fail job failed' }]
|
|
2006
|
+
])
|
|
2007
|
+
})
|
|
2008
|
+
mockResults.set('skipped-job', {
|
|
2009
|
+
job: { name: 'skipped-job', version: '1.0.0', actions: [{ name: 'skipped-action', template: 'template1', arguments: {} }] },
|
|
2010
|
+
outputs: new Map([
|
|
2011
|
+
[1, { status: 'skipped', data: 'Job skipped' }],
|
|
2012
|
+
[137, { status: 'skipped', data: 'Job skipped' }]
|
|
2013
|
+
])
|
|
2014
|
+
})
|
|
2015
|
+
|
|
2016
|
+
// Set the results property
|
|
2017
|
+
;(deployer as any).results = mockResults
|
|
2018
|
+
|
|
2019
|
+
// Call emitRunSummary with hasFailures = true
|
|
2020
|
+
;(deployer as any).emitRunSummary(true)
|
|
2021
|
+
|
|
2022
|
+
// Verify run summary with mixed counts
|
|
2023
|
+
expect(mockEventEmitter.emitEvent).toHaveBeenCalledWith(
|
|
2024
|
+
expect.objectContaining({
|
|
2025
|
+
type: 'run_summary',
|
|
2026
|
+
level: 'warn', // Should be 'warn' due to failures
|
|
2027
|
+
data: expect.objectContaining({
|
|
2028
|
+
networkCount: 2,
|
|
2029
|
+
jobCount: 3,
|
|
2030
|
+
successCount: 2, // success-job on both networks
|
|
2031
|
+
failedCount: 2, // fail-job on both networks
|
|
2032
|
+
skippedCount: 2, // skipped-job on both networks
|
|
2033
|
+
keyContracts: expect.arrayContaining([
|
|
2034
|
+
{ job: 'success-job', action: 'success-action', address: '0x1234567890123456789012345678901234567890' }
|
|
2035
|
+
])
|
|
2036
|
+
})
|
|
2037
|
+
})
|
|
2038
|
+
)
|
|
2039
|
+
})
|
|
2040
|
+
|
|
2041
|
+
it('should handle empty results gracefully', () => {
|
|
2042
|
+
// Set empty results
|
|
2043
|
+
;(deployer as any).results = new Map()
|
|
2044
|
+
|
|
2045
|
+
// Call emitRunSummary
|
|
2046
|
+
;(deployer as any).emitRunSummary(false)
|
|
2047
|
+
|
|
2048
|
+
// Verify run summary with empty results
|
|
2049
|
+
expect(mockEventEmitter.emitEvent).toHaveBeenCalledWith(
|
|
2050
|
+
expect.objectContaining({
|
|
2051
|
+
type: 'run_summary',
|
|
2052
|
+
level: 'info',
|
|
2053
|
+
data: expect.objectContaining({
|
|
2054
|
+
networkCount: 2,
|
|
2055
|
+
jobCount: 0,
|
|
2056
|
+
successCount: 0,
|
|
2057
|
+
failedCount: 0,
|
|
2058
|
+
skippedCount: 0,
|
|
2059
|
+
keyContracts: []
|
|
2060
|
+
})
|
|
2061
|
+
})
|
|
2062
|
+
)
|
|
2063
|
+
})
|
|
2064
|
+
})
|
|
1651
2065
|
})
|
package/src/lib/core/engine.ts
CHANGED
|
@@ -1632,7 +1632,7 @@ export class ExecutionEngine {
|
|
|
1632
1632
|
/**
|
|
1633
1633
|
* Evaluates a list of conditions and returns true if any of them are met.
|
|
1634
1634
|
*/
|
|
1635
|
-
|
|
1635
|
+
public async evaluateSkipConditions(
|
|
1636
1636
|
conditions: Condition[] | undefined,
|
|
1637
1637
|
context: ExecutionContext,
|
|
1638
1638
|
scope: ResolutionScope,
|