@@ -700,13 +700,23 @@ describe('Models/Queue', function() {
700
700
it ( '#addWorker() and removeWorker() should pass calls through to Worker class' , async ( ) => {
701
701
702
702
const queue = await QueueFactory ( ) ;
703
- const workerOptions = { concurrency : 4 } ;
703
+ const workerOptions = {
704
+ concurrency : 4 ,
705
+ onSuccess : async ( id , payload ) => { }
706
+ } ;
704
707
705
708
queue . addWorker ( 'job-name' , ( ) => { } , workerOptions ) ;
706
709
707
710
// first worker is added with default options.
708
711
Worker . workers [ 'job-name' ] . should . be . a . Function ( ) ;
709
- Worker . workers [ 'job-name' ] . options . should . deepEqual ( workerOptions ) ;
712
+ Worker . workers [ 'job-name' ] . options . should . deepEqual ( {
713
+ concurrency : workerOptions . concurrency ,
714
+ onStart : null ,
715
+ onSuccess : workerOptions . onSuccess ,
716
+ onFailure : null ,
717
+ onFailed : null ,
718
+ onComplete : null
719
+ } ) ;
710
720
711
721
queue . removeWorker ( 'job-name' ) ;
712
722
@@ -1581,4 +1591,489 @@ describe('Models/Queue', function() {
1581
1591
1582
1592
} ) ;
1583
1593
1594
+ ////
1595
+ //// JOB LIFECYCLE CALLBACK TESTING
1596
+ ////
1597
+
1598
+ it ( 'onStart lifecycle callback fires before job begins processing.' , async ( ) => {
1599
+
1600
+ const queue = await QueueFactory ( ) ;
1601
+ queue . flushQueue ( ) ;
1602
+ const jobName = 'job-name' ;
1603
+ let jobProcessed = false ;
1604
+ let testFailed = false ;
1605
+
1606
+ queue . addWorker ( jobName , async ( id , payload ) => {
1607
+
1608
+ // Timeout needed because onStart runs async so we need to ensure this function gets
1609
+ // executed last.
1610
+ await new Promise ( ( resolve ) => {
1611
+ setTimeout ( ( ) => {
1612
+ jobProcessed = true ;
1613
+ resolve ( ) ;
1614
+ } , 0 ) ;
1615
+ } ) ;
1616
+
1617
+ } , {
1618
+ onStart : ( id , payload ) => {
1619
+
1620
+ // If onStart runs after job has processed, fail test.
1621
+ if ( jobProcessed ) {
1622
+ testFailed = true ;
1623
+ throw new Error ( 'ERROR: onStart fired after job began processing.' )
1624
+ }
1625
+
1626
+ }
1627
+ } ) ;
1628
+
1629
+ // Create a job
1630
+ queue . createJob ( jobName , { random : 'this is 1st random data' } , { } , false ) ;
1631
+
1632
+ jobProcessed . should . equal ( false ) ;
1633
+ testFailed . should . equal ( false ) ;
1634
+ await queue . start ( ) ;
1635
+ jobProcessed . should . equal ( true ) ;
1636
+ testFailed . should . equal ( false ) ;
1637
+
1638
+ } ) ;
1639
+
1640
+ it ( 'onSuccess, onComplete lifecycle callbacks fire after job begins processing.' , async ( ) => {
1641
+
1642
+ const queue = await QueueFactory ( ) ;
1643
+ queue . flushQueue ( ) ;
1644
+ const jobName = 'job-name' ;
1645
+ let jobProcessed = false ;
1646
+ let testFailed = false ;
1647
+ let onSuccessFired = false ;
1648
+ let onCompleteFired = false ;
1649
+
1650
+ queue . addWorker ( jobName , async ( id , payload ) => {
1651
+
1652
+ // Simulate work
1653
+ await new Promise ( ( resolve ) => {
1654
+ setTimeout ( ( ) => {
1655
+ jobProcessed = true ;
1656
+ resolve ( ) ;
1657
+ } , 300 ) ;
1658
+ } ) ;
1659
+
1660
+ } , {
1661
+ onSuccess : ( id , payload ) => {
1662
+
1663
+ onSuccessFired = true ;
1664
+
1665
+ // If onSuccess runs before job has processed, fail test.
1666
+ if ( ! jobProcessed ) {
1667
+ testFailed = true ;
1668
+ throw new Error ( 'ERROR: onSuccess fired before job began processing.' )
1669
+ }
1670
+
1671
+ } ,
1672
+ onComplete : ( id , payload ) => {
1673
+
1674
+ onCompleteFired = true ;
1675
+
1676
+ // If onComplete runs before job has processed, fail test.
1677
+ if ( ! jobProcessed ) {
1678
+ testFailed = true ;
1679
+ throw new Error ( 'ERROR: onComplete fired before job began processing.' )
1680
+ }
1681
+
1682
+ }
1683
+ } ) ;
1684
+
1685
+ // Create a job
1686
+ queue . createJob ( jobName , { random : 'this is 1st random data' } , { } , false ) ;
1687
+
1688
+ jobProcessed . should . equal ( false ) ;
1689
+ testFailed . should . equal ( false ) ;
1690
+ onSuccessFired . should . equal ( false ) ;
1691
+ onCompleteFired . should . equal ( false ) ;
1692
+ await queue . start ( ) ;
1693
+ jobProcessed . should . equal ( true ) ;
1694
+ testFailed . should . equal ( false ) ;
1695
+ onSuccessFired . should . equal ( true ) ;
1696
+ onCompleteFired . should . equal ( true ) ;
1697
+
1698
+ } ) ;
1699
+
1700
+ it ( 'onFailure, onFailed lifecycle callbacks fire after job begins processing.' , async ( ) => {
1701
+
1702
+ const queue = await QueueFactory ( ) ;
1703
+ queue . flushQueue ( ) ;
1704
+ const jobName = 'job-name' ;
1705
+ let jobProcessStarted = false ;
1706
+ let testFailed = false ;
1707
+
1708
+ queue . addWorker ( jobName , async ( id , payload ) => {
1709
+
1710
+ // Simulate work
1711
+ await new Promise ( ( resolve , reject ) => {
1712
+ setTimeout ( ( ) => {
1713
+ jobProcessStarted = true ;
1714
+ reject ( new Error ( 'Job failed.' ) ) ;
1715
+ } , 300 ) ;
1716
+ } ) ;
1717
+
1718
+ } , {
1719
+ onFailure : ( id , payload ) => {
1720
+
1721
+ // If onFailure runs before job has processed, fail test.
1722
+ if ( ! jobProcessStarted ) {
1723
+ testFailed = true ;
1724
+ throw new Error ( 'ERROR: onFailure fired before job began processing.' )
1725
+ }
1726
+
1727
+ } ,
1728
+ onFailed : ( id , payload ) => {
1729
+
1730
+ // If onFailed runs before job has processed, fail test.
1731
+ if ( ! jobProcessStarted ) {
1732
+ testFailed = true ;
1733
+ throw new Error ( 'ERROR: onFailed fired before job began processing.' )
1734
+ }
1735
+
1736
+ }
1737
+ } ) ;
1738
+
1739
+ // Create a job
1740
+ queue . createJob ( jobName , { random : 'this is 1st random data' } , { } , false ) ;
1741
+
1742
+ jobProcessStarted . should . equal ( false ) ;
1743
+ testFailed . should . equal ( false ) ;
1744
+ await queue . start ( ) ;
1745
+ jobProcessStarted . should . equal ( true ) ;
1746
+ testFailed . should . equal ( false ) ;
1747
+
1748
+ } ) ;
1749
+
1750
+ it ( 'onFailure, onFailed lifecycle callbacks work as expected.' , async ( ) => {
1751
+
1752
+ const queue = await QueueFactory ( ) ;
1753
+ queue . flushQueue ( ) ;
1754
+ const jobName = 'job-name' ;
1755
+ let jobAttemptCounter = 0 ;
1756
+ let onFailureFiredCounter = 0 ;
1757
+ let onFailedFiredCounter = 0 ;
1758
+
1759
+ queue . addWorker ( jobName , async ( id , payload ) => {
1760
+
1761
+ // Simulate work
1762
+ await new Promise ( ( resolve , reject ) => {
1763
+ setTimeout ( ( ) => {
1764
+ jobAttemptCounter ++ ;
1765
+ reject ( new Error ( 'Job failed.' ) ) ;
1766
+ } , 0 ) ;
1767
+ } ) ;
1768
+
1769
+ } , {
1770
+
1771
+ onFailure : ( id , payload ) => {
1772
+
1773
+ onFailureFiredCounter ++ ;
1774
+
1775
+ } ,
1776
+ onFailed : ( id , payload ) => {
1777
+
1778
+ onFailedFiredCounter ++ ;
1779
+
1780
+ }
1781
+ } ) ;
1782
+
1783
+ const attempts = 3 ;
1784
+
1785
+ // Create a job
1786
+ queue . createJob ( jobName , { random : 'this is 1st random data' } , {
1787
+ attempts
1788
+ } , false ) ;
1789
+
1790
+ jobAttemptCounter . should . equal ( 0 ) ;
1791
+ await queue . start ( ) ;
1792
+ onFailureFiredCounter . should . equal ( attempts ) ;
1793
+ onFailedFiredCounter . should . equal ( 1 ) ;
1794
+ jobAttemptCounter . should . equal ( attempts ) ;
1795
+
1796
+ } ) ;
1797
+
1798
+ it ( 'onComplete fires only once on job with multiple attempts that ends in success.' , async ( ) => {
1799
+
1800
+ const queue = await QueueFactory ( ) ;
1801
+ queue . flushQueue ( ) ;
1802
+ const jobName = 'job-name' ;
1803
+ let jobAttemptCounter = 0 ;
1804
+ let onFailureFiredCounter = 0 ;
1805
+ let onFailedFiredCounter = 0 ;
1806
+ let onCompleteFiredCounter = 0 ;
1807
+ const attempts = 3 ;
1808
+
1809
+ queue . addWorker ( jobName , async ( id , payload ) => {
1810
+
1811
+ jobAttemptCounter ++ ;
1812
+
1813
+ // Keep failing attempts until last attempt then success.
1814
+ if ( jobAttemptCounter < attempts ) {
1815
+
1816
+ // Simulate work that fails
1817
+ await new Promise ( ( resolve , reject ) => {
1818
+ setTimeout ( ( ) => {
1819
+ reject ( new Error ( 'Job failed.' ) ) ;
1820
+ } , 0 ) ;
1821
+ } ) ;
1822
+
1823
+ } else {
1824
+
1825
+ // Simulate work that succeeds
1826
+ await new Promise ( ( resolve , reject ) => {
1827
+ setTimeout ( ( ) => {
1828
+ resolve ( ) ;
1829
+ } , 0 ) ;
1830
+ } ) ;
1831
+
1832
+ }
1833
+
1834
+ } , {
1835
+
1836
+ onFailure : ( id , payload ) => {
1837
+
1838
+ onFailureFiredCounter ++ ;
1839
+
1840
+ } ,
1841
+ onFailed : ( id , payload ) => {
1842
+
1843
+ onFailedFiredCounter ++ ;
1844
+
1845
+ } ,
1846
+ onComplete : ( id , payload ) => {
1847
+
1848
+ onCompleteFiredCounter ++ ;
1849
+
1850
+ }
1851
+ } ) ;
1852
+
1853
+ // Create a job
1854
+ queue . createJob ( jobName , { random : 'this is 1st random data succes' } , {
1855
+ attempts
1856
+ } , false ) ;
1857
+
1858
+ jobAttemptCounter . should . equal ( 0 ) ;
1859
+ await queue . start ( ) ;
1860
+ onFailureFiredCounter . should . equal ( attempts - 1 ) ;
1861
+ onFailedFiredCounter . should . equal ( 0 ) ;
1862
+ jobAttemptCounter . should . equal ( attempts ) ;
1863
+ onCompleteFiredCounter . should . equal ( 1 ) ;
1864
+
1865
+ } ) ;
1866
+
1867
+ it ( 'onComplete fires only once on job with multiple attempts that ends in failure.' , async ( ) => {
1868
+
1869
+ const queue = await QueueFactory ( ) ;
1870
+ queue . flushQueue ( ) ;
1871
+ const jobName = 'job-name' ;
1872
+ let jobAttemptCounter = 0 ;
1873
+ let onFailureFiredCounter = 0 ;
1874
+ let onFailedFiredCounter = 0 ;
1875
+ let onCompleteFiredCounter = 0 ;
1876
+ const attempts = 3 ;
1877
+
1878
+ queue . addWorker ( jobName , async ( id , payload ) => {
1879
+
1880
+ jobAttemptCounter ++ ;
1881
+
1882
+ // Simulate work that fails
1883
+ await new Promise ( ( resolve , reject ) => {
1884
+ setTimeout ( ( ) => {
1885
+ reject ( new Error ( 'Job failed.' ) ) ;
1886
+ } , 0 ) ;
1887
+ } ) ;
1888
+
1889
+ } , {
1890
+
1891
+ onFailure : ( id , payload ) => {
1892
+
1893
+ onFailureFiredCounter ++ ;
1894
+
1895
+ } ,
1896
+ onFailed : ( id , payload ) => {
1897
+
1898
+ onFailedFiredCounter ++ ;
1899
+
1900
+ } ,
1901
+ onComplete : ( id , payload ) => {
1902
+
1903
+ onCompleteFiredCounter ++ ;
1904
+
1905
+ }
1906
+ } ) ;
1907
+
1908
+ // Create a job
1909
+ queue . createJob ( jobName , { random : 'this is 1st random data' } , {
1910
+ attempts
1911
+ } , false ) ;
1912
+
1913
+ jobAttemptCounter . should . equal ( 0 ) ;
1914
+ await queue . start ( ) ;
1915
+ onFailureFiredCounter . should . equal ( attempts ) ;
1916
+ onFailedFiredCounter . should . equal ( 1 ) ;
1917
+ jobAttemptCounter . should . equal ( attempts ) ;
1918
+ onCompleteFiredCounter . should . equal ( 1 ) ;
1919
+
1920
+ } ) ;
1921
+
1922
+ it ( 'onStart, onSuccess, onComplete Job lifecycle callbacks do not block job processing.' , async ( ) => {
1923
+
1924
+ const queue = await QueueFactory ( ) ;
1925
+ queue . flushQueue ( ) ;
1926
+ const jobName = 'job-name' ;
1927
+ let workTracker = [ ] ;
1928
+ let tracker = [ ] ;
1929
+
1930
+ queue . addWorker ( jobName , async ( id , payload ) => {
1931
+
1932
+ // Simulate work
1933
+ await new Promise ( ( resolve ) => {
1934
+ workTracker . push ( payload . random ) ;
1935
+ tracker . push ( 'job processed' ) ;
1936
+ setTimeout ( resolve , 0 ) ;
1937
+ } ) ;
1938
+
1939
+ } , {
1940
+
1941
+ onStart : async ( id , payload ) => {
1942
+
1943
+ // wait a bit
1944
+ await new Promise ( ( resolve ) => {
1945
+ setTimeout ( ( ) => {
1946
+ tracker . push ( 'onStart completed.' ) ;
1947
+ } , 1000 ) ;
1948
+ } ) ;
1949
+
1950
+ } ,
1951
+ onSuccess : async ( id , payload ) => {
1952
+
1953
+ // wait a bit
1954
+ await new Promise ( ( resolve ) => {
1955
+ setTimeout ( ( ) => {
1956
+ tracker . push ( 'onSuccess completed.' ) ;
1957
+ } , 1000 ) ;
1958
+ } ) ;
1959
+
1960
+ } ,
1961
+ onComplete : async ( id , payload ) => {
1962
+
1963
+ // wait a bit
1964
+ await new Promise ( ( resolve ) => {
1965
+ setTimeout ( ( ) => {
1966
+ tracker . push ( 'onComplete completed.' ) ;
1967
+ } , 1000 ) ;
1968
+ } ) ;
1969
+
1970
+ }
1971
+ } ) ;
1972
+
1973
+ // Create a job
1974
+ queue . createJob ( jobName , { random : 'this is 1st random data' } , { } , false ) ;
1975
+ queue . createJob ( jobName , { random : 'this is 2nd random data' } , { } , false ) ;
1976
+ queue . createJob ( jobName , { random : 'this is 3rd random data' } , { } , false ) ;
1977
+ queue . createJob ( jobName , { random : 'this is 4th random data' } , { } , false ) ;
1978
+ queue . createJob ( jobName , { random : 'this is 5th random data' } , { } , false ) ;
1979
+
1980
+ await queue . start ( ) ;
1981
+
1982
+ // Ensure all jobs processed.
1983
+ workTracker . should . containDeep ( [
1984
+ 'this is 1st random data' ,
1985
+ 'this is 2nd random data' ,
1986
+ 'this is 4th random data' ,
1987
+ 'this is 3rd random data' ,
1988
+ 'this is 5th random data'
1989
+ ] ) ;
1990
+
1991
+ // Since lifecycle callbacks take a second to process,
1992
+ // queue should churn through all jobs well before any of the lifecycle
1993
+ // callbacks complete.
1994
+ const firstFive = tracker . slice ( 0 , 5 ) ;
1995
+ firstFive . should . deepEqual ( [
1996
+ 'job processed' ,
1997
+ 'job processed' ,
1998
+ 'job processed' ,
1999
+ 'job processed' ,
2000
+ 'job processed'
2001
+ ] ) ;
2002
+
2003
+ } ) ;
2004
+
2005
+ it ( 'onFailure, onFailed Job lifecycle callbacks do not block job processing.' , async ( ) => {
2006
+
2007
+ const queue = await QueueFactory ( ) ;
2008
+ queue . flushQueue ( ) ;
2009
+ const jobName = 'job-name' ;
2010
+ let workTracker = [ ] ;
2011
+ let tracker = [ ] ;
2012
+
2013
+ queue . addWorker ( jobName , async ( id , payload ) => {
2014
+
2015
+ // Simulate failure
2016
+ await new Promise ( ( resolve , reject ) => {
2017
+ workTracker . push ( payload . random ) ;
2018
+ setTimeout ( ( ) => {
2019
+ tracker . push ( 'job attempted' ) ;
2020
+ reject ( new Error ( 'job failed' ) ) ;
2021
+ } , 0 ) ;
2022
+ } ) ;
2023
+
2024
+ } , {
2025
+ onFailure : async ( id , payload ) => {
2026
+
2027
+ // wait a bit
2028
+ await new Promise ( ( resolve ) => {
2029
+ setTimeout ( ( ) => {
2030
+ tracker . push ( 'onFailure completed.' ) ;
2031
+ } , 1000 ) ;
2032
+ } ) ;
2033
+
2034
+ } ,
2035
+ onFailed : async ( id , payload ) => {
2036
+
2037
+ // wait a bit
2038
+ await new Promise ( ( resolve ) => {
2039
+ setTimeout ( ( ) => {
2040
+ tracker . push ( 'onFailed completed.' ) ;
2041
+ } , 1000 ) ;
2042
+ } ) ;
2043
+
2044
+ }
2045
+ } ) ;
2046
+
2047
+ // Create a job
2048
+ queue . createJob ( jobName , { random : 'this is 1st random data' } , { } , false ) ;
2049
+ queue . createJob ( jobName , { random : 'this is 2nd random data' } , { } , false ) ;
2050
+ queue . createJob ( jobName , { random : 'this is 3rd random data' } , { } , false ) ;
2051
+ queue . createJob ( jobName , { random : 'this is 4th random data' } , { } , false ) ;
2052
+ queue . createJob ( jobName , { random : 'this is 5th random data' } , { } , false ) ;
2053
+
2054
+ await queue . start ( ) ;
2055
+
2056
+ // Ensure all jobs started to process (even though they are failed).
2057
+ workTracker . should . containDeep ( [
2058
+ 'this is 1st random data' ,
2059
+ 'this is 2nd random data' ,
2060
+ 'this is 4th random data' ,
2061
+ 'this is 3rd random data' ,
2062
+ 'this is 5th random data'
2063
+ ] ) ;
2064
+
2065
+ // Since lifecycle callbacks take a second to process,
2066
+ // queue should churn through all jobs well before any of the lifecycle
2067
+ // callbacks complete.
2068
+ const firstFive = tracker . slice ( 0 , 5 ) ;
2069
+ firstFive . should . deepEqual ( [
2070
+ 'job attempted' ,
2071
+ 'job attempted' ,
2072
+ 'job attempted' ,
2073
+ 'job attempted' ,
2074
+ 'job attempted'
2075
+ ] ) ;
2076
+
2077
+ } ) ;
2078
+
1584
2079
} ) ;
0 commit comments