@nicnocquee/dataqueue 1.19.3 → 1.21.0

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/src/queue.test.ts CHANGED
@@ -5,8 +5,6 @@ import { createTestDbAndPool, destroyTestDb } from './test-util.js';
5
5
  import { JobEvent, JobEventType } from './types.js';
6
6
  import { objectKeysToCamelCase } from './utils.js';
7
7
 
8
- // Example integration test setup
9
-
10
8
  describe('queue integration', () => {
11
9
  let pool: Pool;
12
10
  let dbName: string;
@@ -501,3 +499,701 @@ describe('job lifecycle timestamp columns', () => {
501
499
  expect(job.lastRetriedAt).not.toBeNull();
502
500
  });
503
501
  });
502
+
503
+ describe('tags feature', () => {
504
+ let pool: Pool;
505
+ let dbName: string;
506
+
507
+ beforeEach(async () => {
508
+ const setup = await createTestDbAndPool();
509
+ pool = setup.pool;
510
+ dbName = setup.dbName;
511
+ });
512
+
513
+ afterEach(async () => {
514
+ await pool.end();
515
+ await destroyTestDb(dbName);
516
+ });
517
+
518
+ it('should add a job with tags and retrieve it by tags (all mode)', async () => {
519
+ const jobId = await queue.addJob<{ email: { to: string } }, 'email'>(pool, {
520
+ jobType: 'email',
521
+ payload: { to: 'tagged@example.com' },
522
+ tags: ['welcome', 'user:1'],
523
+ });
524
+ const jobs = await queue.getJobsByTags(pool, ['welcome'], 'all');
525
+ expect(jobs.map((j) => j.id)).toContain(jobId);
526
+ expect(jobs[0].tags).toContain('welcome');
527
+ expect(jobs[0].tags).toContain('user:1');
528
+ });
529
+
530
+ it('should only return jobs that match all specified tags (all mode)', async () => {
531
+ const jobId1 = await queue.addJob<{ email: { to: string } }, 'email'>(
532
+ pool,
533
+ {
534
+ jobType: 'email',
535
+ payload: { to: 'a@example.com' },
536
+ tags: ['foo', 'bar'],
537
+ },
538
+ );
539
+ const jobId2 = await queue.addJob<{ email: { to: string } }, 'email'>(
540
+ pool,
541
+ {
542
+ jobType: 'email',
543
+ payload: { to: 'b@example.com' },
544
+ tags: ['foo'],
545
+ },
546
+ );
547
+
548
+ const jobId3 = await queue.addJob<{ email: { to: string } }, 'email'>(
549
+ pool,
550
+ {
551
+ jobType: 'email',
552
+ payload: { to: 'c@example.com' },
553
+ tags: ['foo', 'bar', 'baz'],
554
+ },
555
+ );
556
+ const jobs = await queue.getJobsByTags(pool, ['foo', 'bar'], 'all');
557
+ expect(jobs.map((j) => j.id)).toContain(jobId1);
558
+ expect(jobs.map((j) => j.id)).not.toContain(jobId2);
559
+ expect(jobs.map((j) => j.id)).toContain(jobId3);
560
+ });
561
+
562
+ it('should return jobs with exactly the same tags (exact mode)', async () => {
563
+ const jobId1 = await queue.addJob<{ email: { to: string } }, 'email'>(
564
+ pool,
565
+ {
566
+ jobType: 'email',
567
+ payload: { to: 'a@example.com' },
568
+ tags: ['foo', 'bar'],
569
+ },
570
+ );
571
+ const jobId2 = await queue.addJob<{ email: { to: string } }, 'email'>(
572
+ pool,
573
+ {
574
+ jobType: 'email',
575
+ payload: { to: 'b@example.com' },
576
+ tags: ['foo', 'bar', 'baz'],
577
+ },
578
+ );
579
+ const jobs = await queue.getJobsByTags(pool, ['foo', 'bar'], 'exact');
580
+ expect(jobs.map((j) => j.id)).toContain(jobId1);
581
+ expect(jobs.map((j) => j.id)).not.toContain(jobId2);
582
+ });
583
+
584
+ it('should return jobs that have any of the given tags (any mode)', async () => {
585
+ const jobId1 = await queue.addJob<{ email: { to: string } }, 'email'>(
586
+ pool,
587
+ {
588
+ jobType: 'email',
589
+ payload: { to: 'a@example.com' },
590
+ tags: ['foo', 'bar'],
591
+ },
592
+ );
593
+ const jobId2 = await queue.addJob<{ email: { to: string } }, 'email'>(
594
+ pool,
595
+ {
596
+ jobType: 'email',
597
+ payload: { to: 'b@example.com' },
598
+ tags: ['baz'],
599
+ },
600
+ );
601
+ const jobs = await queue.getJobsByTags(pool, ['bar', 'baz'], 'any');
602
+ expect(jobs.map((j) => j.id)).toContain(jobId1);
603
+ expect(jobs.map((j) => j.id)).toContain(jobId2);
604
+ });
605
+
606
+ it('should return jobs that have none of the given tags (none mode)', async () => {
607
+ const jobId1 = await queue.addJob<{ email: { to: string } }, 'email'>(
608
+ pool,
609
+ {
610
+ jobType: 'email',
611
+ payload: { to: 'a@example.com' },
612
+ tags: ['foo'],
613
+ },
614
+ );
615
+ const jobId2 = await queue.addJob<{ email: { to: string } }, 'email'>(
616
+ pool,
617
+ {
618
+ jobType: 'email',
619
+ payload: { to: 'b@example.com' },
620
+ tags: ['bar'],
621
+ },
622
+ );
623
+ const jobId3 = await queue.addJob<{ email: { to: string } }, 'email'>(
624
+ pool,
625
+ {
626
+ jobType: 'email',
627
+ payload: { to: 'c@example.com' },
628
+ tags: ['baz'],
629
+ },
630
+ );
631
+ const jobs = await queue.getJobsByTags(pool, ['foo', 'bar'], 'none');
632
+ expect(jobs.map((j) => j.id)).toContain(jobId3);
633
+ expect(jobs.map((j) => j.id)).not.toContain(jobId1);
634
+ expect(jobs.map((j) => j.id)).not.toContain(jobId2);
635
+ });
636
+
637
+ it('should handle jobs with no tags', async () => {
638
+ const jobId = await queue.addJob<{ email: { to: string } }, 'email'>(pool, {
639
+ jobType: 'email',
640
+ payload: { to: 'notag@example.com' },
641
+ });
642
+ const jobs = await queue.getJobsByTags(pool, ['anytag'], 'all');
643
+ expect(jobs.map((j) => j.id)).not.toContain(jobId);
644
+ });
645
+
646
+ it('should cancel jobs by tags (all mode)', async () => {
647
+ const jobId = await queue.addJob<{ email: { to: string } }, 'email'>(pool, {
648
+ jobType: 'email',
649
+ payload: { to: 'cancelme@example.com' },
650
+ tags: ['cancel', 'urgent'],
651
+ });
652
+ const cancelled = await queue.cancelAllUpcomingJobs(pool, {
653
+ tags: { values: ['cancel', 'urgent'], mode: 'all' },
654
+ });
655
+ expect(cancelled).toBeGreaterThanOrEqual(1);
656
+ const job = await queue.getJob(pool, jobId);
657
+ expect(job?.status).toBe('cancelled');
658
+ });
659
+
660
+ it('should cancel jobs by tags (exact mode)', async () => {
661
+ const jobId1 = await queue.addJob<{ email: { to: string } }, 'email'>(
662
+ pool,
663
+ {
664
+ jobType: 'email',
665
+ payload: { to: 'cancel1@example.com' },
666
+ tags: ['cancel', 'urgent'],
667
+ },
668
+ );
669
+ const jobId2 = await queue.addJob<{ email: { to: string } }, 'email'>(
670
+ pool,
671
+ {
672
+ jobType: 'email',
673
+ payload: { to: 'cancel2@example.com' },
674
+ tags: ['cancel', 'urgent', 'other'],
675
+ },
676
+ );
677
+ const cancelled = await queue.cancelAllUpcomingJobs(pool, {
678
+ tags: { values: ['cancel', 'urgent'], mode: 'exact' },
679
+ });
680
+ expect(cancelled).toBe(1);
681
+ const job1 = await queue.getJob(pool, jobId1);
682
+ const job2 = await queue.getJob(pool, jobId2);
683
+ expect(job1?.status).toBe('cancelled');
684
+ expect(job2?.status).toBe('pending');
685
+ });
686
+
687
+ it('should cancel jobs by tags (any mode)', async () => {
688
+ const jobId1 = await queue.addJob<{ email: { to: string } }, 'email'>(
689
+ pool,
690
+ {
691
+ jobType: 'email',
692
+ payload: { to: 'cancel1@example.com' },
693
+ tags: ['cancel', 'urgent'],
694
+ },
695
+ );
696
+ const jobId2 = await queue.addJob<{ email: { to: string } }, 'email'>(
697
+ pool,
698
+ {
699
+ jobType: 'email',
700
+ payload: { to: 'cancel2@example.com' },
701
+ tags: ['other'],
702
+ },
703
+ );
704
+ const cancelled = await queue.cancelAllUpcomingJobs(pool, {
705
+ tags: { values: ['cancel', 'other'], mode: 'any' },
706
+ });
707
+ expect(cancelled).toBe(2);
708
+ const job1 = await queue.getJob(pool, jobId1);
709
+ const job2 = await queue.getJob(pool, jobId2);
710
+ expect(job1?.status).toBe('cancelled');
711
+ expect(job2?.status).toBe('cancelled');
712
+ });
713
+
714
+ it('should cancel jobs by tags (none mode)', async () => {
715
+ const jobId1 = await queue.addJob<{ email: { to: string } }, 'email'>(
716
+ pool,
717
+ {
718
+ jobType: 'email',
719
+ payload: { to: 'cancel1@example.com' },
720
+ tags: ['foo'],
721
+ },
722
+ );
723
+ const jobId2 = await queue.addJob<{ email: { to: string } }, 'email'>(
724
+ pool,
725
+ {
726
+ jobType: 'email',
727
+ payload: { to: 'cancel2@example.com' },
728
+ tags: ['bar'],
729
+ },
730
+ );
731
+ const jobId3 = await queue.addJob<{ email: { to: string } }, 'email'>(
732
+ pool,
733
+ {
734
+ jobType: 'email',
735
+ payload: { to: 'keep@example.com' },
736
+ tags: ['baz'],
737
+ },
738
+ );
739
+ const cancelled = await queue.cancelAllUpcomingJobs(pool, {
740
+ tags: { values: ['foo', 'bar'], mode: 'none' },
741
+ });
742
+ expect(cancelled).toBe(1);
743
+ const job1 = await queue.getJob(pool, jobId1);
744
+ const job2 = await queue.getJob(pool, jobId2);
745
+ const job3 = await queue.getJob(pool, jobId3);
746
+ expect(job1?.status).toBe('pending');
747
+ expect(job2?.status).toBe('pending');
748
+ expect(job3?.status).toBe('cancelled');
749
+ });
750
+ });
751
+
752
+ describe('cancelAllUpcomingJobs with runAt object filter', () => {
753
+ let pool: Pool;
754
+ let dbName: string;
755
+
756
+ beforeEach(async () => {
757
+ const setup = await createTestDbAndPool();
758
+ pool = setup.pool;
759
+ dbName = setup.dbName;
760
+ });
761
+
762
+ afterEach(async () => {
763
+ await pool.end();
764
+ await destroyTestDb(dbName);
765
+ });
766
+
767
+ it('should cancel jobs with runAt > filter (gt)', async () => {
768
+ const now = new Date();
769
+ const past = new Date(now.getTime() - 24 * 60 * 60 * 1000);
770
+ const future = new Date(now.getTime() + 24 * 60 * 60 * 1000);
771
+ const jobIdPast = await queue.addJob<{ email: { to: string } }, 'email'>(
772
+ pool,
773
+ {
774
+ jobType: 'email',
775
+ payload: { to: 'past@example.com' },
776
+ runAt: past,
777
+ },
778
+ );
779
+ const jobIdNow = await queue.addJob<{ email: { to: string } }, 'email'>(
780
+ pool,
781
+ {
782
+ jobType: 'email',
783
+ payload: { to: 'now@example.com' },
784
+ runAt: now,
785
+ },
786
+ );
787
+ const jobIdFuture = await queue.addJob<{ email: { to: string } }, 'email'>(
788
+ pool,
789
+ {
790
+ jobType: 'email',
791
+ payload: { to: 'future@example.com' },
792
+ runAt: future,
793
+ },
794
+ );
795
+ const cancelled = await queue.cancelAllUpcomingJobs(pool, {
796
+ runAt: { gt: now },
797
+ });
798
+ expect(cancelled).toBe(1);
799
+ const jobPast = await queue.getJob(pool, jobIdPast);
800
+ const jobNow = await queue.getJob(pool, jobIdNow);
801
+ const jobFuture = await queue.getJob(pool, jobIdFuture);
802
+ expect(jobPast?.status).toBe('pending');
803
+ expect(jobNow?.status).toBe('pending');
804
+ expect(jobFuture?.status).toBe('cancelled');
805
+ });
806
+
807
+ it('should cancel jobs with runAt >= filter (gte)', async () => {
808
+ const now = new Date();
809
+ const past = new Date(now.getTime() - 24 * 60 * 60 * 1000);
810
+ const future = new Date(now.getTime() + 24 * 60 * 60 * 1000);
811
+ const jobIdPast = await queue.addJob<{ email: { to: string } }, 'email'>(
812
+ pool,
813
+ {
814
+ jobType: 'email',
815
+ payload: { to: 'past@example.com' },
816
+ runAt: past,
817
+ },
818
+ );
819
+ const jobIdNow = await queue.addJob<{ email: { to: string } }, 'email'>(
820
+ pool,
821
+ {
822
+ jobType: 'email',
823
+ payload: { to: 'now@example.com' },
824
+ runAt: now,
825
+ },
826
+ );
827
+ const jobIdFuture = await queue.addJob<{ email: { to: string } }, 'email'>(
828
+ pool,
829
+ {
830
+ jobType: 'email',
831
+ payload: { to: 'future@example.com' },
832
+ runAt: future,
833
+ },
834
+ );
835
+ const cancelled = await queue.cancelAllUpcomingJobs(pool, {
836
+ runAt: { gte: now },
837
+ });
838
+ expect(cancelled).toBe(2);
839
+ const jobPast = await queue.getJob(pool, jobIdPast);
840
+ const jobNow = await queue.getJob(pool, jobIdNow);
841
+ const jobFuture = await queue.getJob(pool, jobIdFuture);
842
+ expect(jobPast?.status).toBe('pending');
843
+ expect(jobNow?.status).toBe('cancelled');
844
+ expect(jobFuture?.status).toBe('cancelled');
845
+ });
846
+
847
+ it('should cancel jobs with runAt < filter (lt)', async () => {
848
+ const now = new Date();
849
+ const past = new Date(now.getTime() - 24 * 60 * 60 * 1000);
850
+ const future = new Date(now.getTime() + 24 * 60 * 60 * 1000);
851
+ const jobIdPast = await queue.addJob<{ email: { to: string } }, 'email'>(
852
+ pool,
853
+ {
854
+ jobType: 'email',
855
+ payload: { to: 'past@example.com' },
856
+ runAt: past,
857
+ },
858
+ );
859
+ const jobIdNow = await queue.addJob<{ email: { to: string } }, 'email'>(
860
+ pool,
861
+ {
862
+ jobType: 'email',
863
+ payload: { to: 'now@example.com' },
864
+ runAt: now,
865
+ },
866
+ );
867
+ const jobIdFuture = await queue.addJob<{ email: { to: string } }, 'email'>(
868
+ pool,
869
+ {
870
+ jobType: 'email',
871
+ payload: { to: 'future@example.com' },
872
+ runAt: future,
873
+ },
874
+ );
875
+ const cancelled = await queue.cancelAllUpcomingJobs(pool, {
876
+ runAt: { lt: now },
877
+ });
878
+ expect(cancelled).toBe(1);
879
+ const jobPast = await queue.getJob(pool, jobIdPast);
880
+ const jobNow = await queue.getJob(pool, jobIdNow);
881
+ const jobFuture = await queue.getJob(pool, jobIdFuture);
882
+ expect(jobPast?.status).toBe('cancelled');
883
+ expect(jobNow?.status).toBe('pending');
884
+ expect(jobFuture?.status).toBe('pending');
885
+ });
886
+
887
+ it('should cancel jobs with runAt <= filter (lte)', async () => {
888
+ const now = new Date();
889
+ const past = new Date(now.getTime() - 24 * 60 * 60 * 1000);
890
+ const future = new Date(now.getTime() + 24 * 60 * 60 * 1000);
891
+ const jobIdPast = await queue.addJob<{ email: { to: string } }, 'email'>(
892
+ pool,
893
+ {
894
+ jobType: 'email',
895
+ payload: { to: 'past@example.com' },
896
+ runAt: past,
897
+ },
898
+ );
899
+ const jobIdNow = await queue.addJob<{ email: { to: string } }, 'email'>(
900
+ pool,
901
+ {
902
+ jobType: 'email',
903
+ payload: { to: 'now@example.com' },
904
+ runAt: now,
905
+ },
906
+ );
907
+ const jobIdFuture = await queue.addJob<{ email: { to: string } }, 'email'>(
908
+ pool,
909
+ {
910
+ jobType: 'email',
911
+ payload: { to: 'future@example.com' },
912
+ runAt: future,
913
+ },
914
+ );
915
+ const cancelled = await queue.cancelAllUpcomingJobs(pool, {
916
+ runAt: { lte: now },
917
+ });
918
+ expect(cancelled).toBe(2);
919
+ const jobPast = await queue.getJob(pool, jobIdPast);
920
+ const jobNow = await queue.getJob(pool, jobIdNow);
921
+ const jobFuture = await queue.getJob(pool, jobIdFuture);
922
+ expect(jobPast?.status).toBe('cancelled');
923
+ expect(jobNow?.status).toBe('cancelled');
924
+ expect(jobFuture?.status).toBe('pending');
925
+ });
926
+
927
+ it('should cancel jobs with runAt eq filter (eq)', async () => {
928
+ const now = new Date();
929
+ const past = new Date(now.getTime() - 24 * 60 * 60 * 1000);
930
+ const future = new Date(now.getTime() + 24 * 60 * 60 * 1000);
931
+ const jobIdPast = await queue.addJob<{ email: { to: string } }, 'email'>(
932
+ pool,
933
+ {
934
+ jobType: 'email',
935
+ payload: { to: 'past@example.com' },
936
+ runAt: past,
937
+ },
938
+ );
939
+ const jobIdNow = await queue.addJob<{ email: { to: string } }, 'email'>(
940
+ pool,
941
+ {
942
+ jobType: 'email',
943
+ payload: { to: 'now@example.com' },
944
+ runAt: now,
945
+ },
946
+ );
947
+ const jobIdFuture = await queue.addJob<{ email: { to: string } }, 'email'>(
948
+ pool,
949
+ {
950
+ jobType: 'email',
951
+ payload: { to: 'future@example.com' },
952
+ runAt: future,
953
+ },
954
+ );
955
+ const cancelled = await queue.cancelAllUpcomingJobs(pool, {
956
+ runAt: { eq: now },
957
+ });
958
+ expect(cancelled).toBe(1);
959
+ const jobPast = await queue.getJob(pool, jobIdPast);
960
+ const jobNow = await queue.getJob(pool, jobIdNow);
961
+ const jobFuture = await queue.getJob(pool, jobIdFuture);
962
+ expect(jobPast?.status).toBe('pending');
963
+ expect(jobNow?.status).toBe('cancelled');
964
+ expect(jobFuture?.status).toBe('pending');
965
+ });
966
+ });
967
+
968
+ describe('getJobs', () => {
969
+ let pool: Pool;
970
+ let dbName: string;
971
+
972
+ beforeEach(async () => {
973
+ const setup = await createTestDbAndPool();
974
+ pool = setup.pool;
975
+ dbName = setup.dbName;
976
+ });
977
+
978
+ afterEach(async () => {
979
+ await pool.end();
980
+ await destroyTestDb(dbName);
981
+ });
982
+
983
+ it('should filter by jobType', async () => {
984
+ const id1 = await queue.addJob<{ a: { n: number }; b: { n: number } }, 'a'>(
985
+ pool,
986
+ { jobType: 'a', payload: { n: 1 } },
987
+ );
988
+ const id2 = await queue.addJob<{ a: { n: number }; b: { n: number } }, 'b'>(
989
+ pool,
990
+ { jobType: 'b', payload: { n: 2 } },
991
+ );
992
+ const jobs = await queue.getJobs(pool, { jobType: 'a' });
993
+ expect(jobs.map((j) => j.id)).toContain(id1);
994
+ expect(jobs.map((j) => j.id)).not.toContain(id2);
995
+ });
996
+
997
+ it('should filter by priority', async () => {
998
+ const id1 = await queue.addJob<{ a: { n: number } }, 'a'>(pool, {
999
+ jobType: 'a',
1000
+ payload: { n: 1 },
1001
+ priority: 1,
1002
+ });
1003
+ const id2 = await queue.addJob<{ a: { n: number } }, 'a'>(pool, {
1004
+ jobType: 'a',
1005
+ payload: { n: 2 },
1006
+ priority: 2,
1007
+ });
1008
+ const jobs = await queue.getJobs(pool, { priority: 2 });
1009
+ expect(jobs.map((j) => j.id)).toContain(id2);
1010
+ expect(jobs.map((j) => j.id)).not.toContain(id1);
1011
+ });
1012
+
1013
+ it('should filter by runAt', async () => {
1014
+ const runAt = new Date(Date.UTC(2030, 0, 1, 12, 0, 0, 0));
1015
+ const id1 = await queue.addJob<{ a: { n: number } }, 'a'>(pool, {
1016
+ jobType: 'a',
1017
+ payload: { n: 1 },
1018
+ runAt,
1019
+ });
1020
+ const id2 = await queue.addJob<{ a: { n: number } }, 'a'>(pool, {
1021
+ jobType: 'a',
1022
+ payload: { n: 2 },
1023
+ });
1024
+ const jobs = await queue.getJobs(pool, { runAt });
1025
+ expect(jobs.map((j) => j.id)).toContain(id1);
1026
+ expect(jobs.map((j) => j.id)).not.toContain(id2);
1027
+ });
1028
+
1029
+ it('should filter jobs using runAt with gt/gte/lt/lte/eq', async () => {
1030
+ const now = new Date();
1031
+ const past = new Date(now.getTime() - 24 * 60 * 60 * 1000); // 1 day ago
1032
+ const future = new Date(now.getTime() + 24 * 60 * 60 * 1000); // 1 day ahead
1033
+ const jobIdPast = await queue.addJob<{ email: { to: string } }, 'email'>(
1034
+ pool,
1035
+ {
1036
+ jobType: 'email',
1037
+ payload: { to: 'past@example.com' },
1038
+ runAt: past,
1039
+ },
1040
+ );
1041
+ const jobIdNow = await queue.addJob<{ email: { to: string } }, 'email'>(
1042
+ pool,
1043
+ {
1044
+ jobType: 'email',
1045
+ payload: { to: 'now@example.com' },
1046
+ runAt: now,
1047
+ },
1048
+ );
1049
+ const jobIdFuture = await queue.addJob<{ email: { to: string } }, 'email'>(
1050
+ pool,
1051
+ {
1052
+ jobType: 'email',
1053
+ payload: { to: 'future@example.com' },
1054
+ runAt: future,
1055
+ },
1056
+ );
1057
+ // eq
1058
+ let jobs = await queue.getJobs(pool, { runAt: now });
1059
+ expect(jobs.map((j) => j.id)).toContain(jobIdNow);
1060
+ // gt
1061
+ jobs = await queue.getJobs(pool, { runAt: { gt: now } });
1062
+ expect(jobs.map((j) => j.id)).toContain(jobIdFuture);
1063
+ expect(jobs.map((j) => j.id)).not.toContain(jobIdNow);
1064
+ // gte
1065
+ jobs = await queue.getJobs(pool, { runAt: { gte: now } });
1066
+ expect(jobs.map((j) => j.id)).toContain(jobIdNow);
1067
+ expect(jobs.map((j) => j.id)).toContain(jobIdFuture);
1068
+ // lt
1069
+ jobs = await queue.getJobs(pool, { runAt: { lt: now } });
1070
+ expect(jobs.map((j) => j.id)).toContain(jobIdPast);
1071
+ expect(jobs.map((j) => j.id)).not.toContain(jobIdNow);
1072
+ // lte
1073
+ jobs = await queue.getJobs(pool, { runAt: { lte: now } });
1074
+ expect(jobs.map((j) => j.id)).toContain(jobIdPast);
1075
+ expect(jobs.map((j) => j.id)).toContain(jobIdNow);
1076
+ });
1077
+
1078
+ it('should filter by tags (all mode)', async () => {
1079
+ const id1 = await queue.addJob<{ a: { n: number } }, 'a'>(pool, {
1080
+ jobType: 'a',
1081
+ payload: { n: 1 },
1082
+ tags: ['foo', 'bar'],
1083
+ });
1084
+ const id2 = await queue.addJob<{ a: { n: number } }, 'a'>(pool, {
1085
+ jobType: 'a',
1086
+ payload: { n: 2 },
1087
+ tags: ['foo'],
1088
+ });
1089
+ const jobs = await queue.getJobs(pool, {
1090
+ tags: { values: ['foo', 'bar'], mode: 'all' },
1091
+ });
1092
+ expect(jobs.map((j) => j.id)).toContain(id1);
1093
+ expect(jobs.map((j) => j.id)).not.toContain(id2);
1094
+ });
1095
+
1096
+ it('should filter by tags (any mode)', async () => {
1097
+ const id1 = await queue.addJob<{ a: { n: number } }, 'a'>(pool, {
1098
+ jobType: 'a',
1099
+ payload: { n: 1 },
1100
+ tags: ['foo'],
1101
+ });
1102
+ const id2 = await queue.addJob<{ a: { n: number } }, 'a'>(pool, {
1103
+ jobType: 'a',
1104
+ payload: { n: 2 },
1105
+ tags: ['bar'],
1106
+ });
1107
+ const jobs = await queue.getJobs(pool, {
1108
+ tags: { values: ['foo', 'bar'], mode: 'any' },
1109
+ });
1110
+ expect(jobs.map((j) => j.id)).toContain(id1);
1111
+ expect(jobs.map((j) => j.id)).toContain(id2);
1112
+ });
1113
+
1114
+ it('should filter by tags (exact mode)', async () => {
1115
+ const id1 = await queue.addJob<{ a: { n: number } }, 'a'>(pool, {
1116
+ jobType: 'a',
1117
+ payload: { n: 1 },
1118
+ tags: ['foo', 'bar'],
1119
+ });
1120
+ const id2 = await queue.addJob<{ a: { n: number } }, 'a'>(pool, {
1121
+ jobType: 'a',
1122
+ payload: { n: 2 },
1123
+ tags: ['foo', 'bar', 'baz'],
1124
+ });
1125
+ const jobs = await queue.getJobs(pool, {
1126
+ tags: { values: ['foo', 'bar'], mode: 'exact' },
1127
+ });
1128
+ expect(jobs.map((j) => j.id)).toContain(id1);
1129
+ expect(jobs.map((j) => j.id)).not.toContain(id2);
1130
+ });
1131
+
1132
+ it('should filter by tags (none mode)', async () => {
1133
+ const id1 = await queue.addJob<{ a: { n: number } }, 'a'>(pool, {
1134
+ jobType: 'a',
1135
+ payload: { n: 1 },
1136
+ tags: ['foo'],
1137
+ });
1138
+ const id2 = await queue.addJob<{ a: { n: number } }, 'a'>(pool, {
1139
+ jobType: 'a',
1140
+ payload: { n: 2 },
1141
+ tags: ['bar'],
1142
+ });
1143
+ const id3 = await queue.addJob<{ a: { n: number } }, 'a'>(pool, {
1144
+ jobType: 'a',
1145
+ payload: { n: 3 },
1146
+ tags: ['baz'],
1147
+ });
1148
+ const jobs = await queue.getJobs(pool, {
1149
+ tags: { values: ['foo', 'bar'], mode: 'none' },
1150
+ });
1151
+ expect(jobs.map((j) => j.id)).toContain(id3);
1152
+ expect(jobs.map((j) => j.id)).not.toContain(id1);
1153
+ expect(jobs.map((j) => j.id)).not.toContain(id2);
1154
+ });
1155
+
1156
+ it('should support pagination', async () => {
1157
+ const ids = [];
1158
+ for (let i = 0; i < 5; i++) {
1159
+ ids.push(
1160
+ await queue.addJob<{ a: { n: number } }, 'a'>(pool, {
1161
+ jobType: 'a',
1162
+ payload: { n: i },
1163
+ }),
1164
+ );
1165
+ }
1166
+ const firstTwo = await queue.getJobs(pool, {}, 2, 0);
1167
+ const nextTwo = await queue.getJobs(pool, {}, 2, 2);
1168
+ expect(firstTwo.length).toBe(2);
1169
+ expect(nextTwo.length).toBe(2);
1170
+ const firstIds = firstTwo.map((j) => j.id);
1171
+ const nextIds = nextTwo.map((j) => j.id);
1172
+ expect(firstIds.some((id) => nextIds.includes(id))).toBe(false);
1173
+ });
1174
+
1175
+ it('should filter by a combination of filters', async () => {
1176
+ const runAt = new Date(Date.UTC(2030, 0, 1, 12, 0, 0, 0));
1177
+ const id1 = await queue.addJob<{ a: { n: number } }, 'a'>(pool, {
1178
+ jobType: 'a',
1179
+ payload: { n: 1 },
1180
+ priority: 1,
1181
+ runAt,
1182
+ tags: ['foo', 'bar'],
1183
+ });
1184
+ const id2 = await queue.addJob<{ a: { n: number } }, 'a'>(pool, {
1185
+ jobType: 'a',
1186
+ payload: { n: 2 },
1187
+ priority: 2,
1188
+ tags: ['foo'],
1189
+ });
1190
+ const jobs = await queue.getJobs(pool, {
1191
+ jobType: 'a',
1192
+ priority: 1,
1193
+ runAt,
1194
+ tags: { values: ['foo', 'bar'], mode: 'all' },
1195
+ });
1196
+ expect(jobs.map((j) => j.id)).toContain(id1);
1197
+ expect(jobs.map((j) => j.id)).not.toContain(id2);
1198
+ });
1199
+ });