@wavezync/nestjs-pgboss 5.1.0 → 6.0.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.
@@ -0,0 +1,123 @@
1
+ jest.mock("pg-boss", () => {
2
+ return {
3
+ PgBoss: jest.fn().mockImplementation(() => ({
4
+ on: jest.fn(),
5
+ start: jest.fn(),
6
+ stop: jest.fn(),
7
+ })),
8
+ };
9
+ });
10
+
11
+ import { PgBossModule } from "../lib/pgboss.module";
12
+ import { HandlerScannerService } from "../lib/handler-scanner.service";
13
+ import { PgBossService } from "../lib/pgboss.service";
14
+ import { PGBOSS_OPTIONS, PGBOSS_TOKEN } from "../lib/utils/consts";
15
+ import { MetadataScanner } from "@nestjs/core";
16
+
17
+ describe("PgBossModule", () => {
18
+ let mockBoss: any;
19
+ let mockHandlerScanner: jest.Mocked<
20
+ Pick<HandlerScannerService, "scanAndRegisterHandlers">
21
+ >;
22
+ let module: PgBossModule;
23
+
24
+ beforeEach(() => {
25
+ mockBoss = {
26
+ on: jest.fn(),
27
+ stop: jest.fn().mockResolvedValue(undefined),
28
+ };
29
+ mockHandlerScanner = {
30
+ scanAndRegisterHandlers: jest.fn().mockResolvedValue(undefined),
31
+ };
32
+ module = new PgBossModule(mockBoss, mockHandlerScanner as any);
33
+ });
34
+
35
+ it("should register error event listener on boss in constructor", () => {
36
+ expect(mockBoss.on).toHaveBeenCalledWith("error", expect.any(Function));
37
+ });
38
+
39
+ describe("onApplicationBootstrap", () => {
40
+ it("should call scanAndRegisterHandlers", async () => {
41
+ await module.onApplicationBootstrap();
42
+ expect(mockHandlerScanner.scanAndRegisterHandlers).toHaveBeenCalled();
43
+ });
44
+ });
45
+
46
+ describe("onModuleDestroy", () => {
47
+ it("should call boss.stop()", async () => {
48
+ await module.onModuleDestroy();
49
+ expect(mockBoss.stop).toHaveBeenCalled();
50
+ });
51
+
52
+ it("should catch and log errors from boss.stop()", async () => {
53
+ mockBoss.stop.mockRejectedValue(new Error("stop failed"));
54
+ // Should not throw
55
+ await expect(module.onModuleDestroy()).resolves.not.toThrow();
56
+ });
57
+ });
58
+
59
+ describe("forRootAsync", () => {
60
+ it("should return correct module structure with useFactory", () => {
61
+ const factory = jest.fn();
62
+ const result = PgBossModule.forRootAsync({
63
+ useFactory: factory,
64
+ inject: ["CONFIG"],
65
+ });
66
+
67
+ expect(result.module).toBe(PgBossModule);
68
+ expect(result.exports).toContain(PgBossService);
69
+ expect(result.exports).toContain(PGBOSS_TOKEN);
70
+
71
+ const providerTokens = (result.providers as any[]).map(
72
+ (p: any) => p.provide || p,
73
+ );
74
+ expect(providerTokens).toContain(PGBOSS_OPTIONS);
75
+ expect(providerTokens).toContain(PGBOSS_TOKEN);
76
+ expect(providerTokens).toContain(PgBossService);
77
+ expect(providerTokens).toContain(HandlerScannerService);
78
+ expect(providerTokens).toContain(MetadataScanner);
79
+ });
80
+
81
+ it("should return correct module structure with useClass", () => {
82
+ class TestOptionsFactory {
83
+ createPgBossOptions() {
84
+ return { connectionString: "postgres://localhost/test" };
85
+ }
86
+ }
87
+
88
+ const result = PgBossModule.forRootAsync({
89
+ useClass: TestOptionsFactory,
90
+ });
91
+
92
+ expect(result.module).toBe(PgBossModule);
93
+ expect(result.exports).toContain(PgBossService);
94
+ expect(result.exports).toContain(PGBOSS_TOKEN);
95
+
96
+ const providerTokens = (result.providers as any[]).map(
97
+ (p: any) => p.provide || p,
98
+ );
99
+ expect(providerTokens).toContain(PGBOSS_OPTIONS);
100
+ expect(providerTokens).toContain(PGBOSS_TOKEN);
101
+ // useClass also adds the class itself as a provider
102
+ expect(providerTokens).toContain(TestOptionsFactory);
103
+ });
104
+
105
+ it("should pass imports through", () => {
106
+ const fakeModule = { module: class FakeModule {} } as any;
107
+ const result = PgBossModule.forRootAsync({
108
+ imports: [fakeModule],
109
+ useFactory: jest.fn(),
110
+ });
111
+
112
+ expect(result.imports).toContain(fakeModule);
113
+ });
114
+
115
+ it("should default imports to empty array", () => {
116
+ const result = PgBossModule.forRootAsync({
117
+ useFactory: jest.fn(),
118
+ });
119
+
120
+ expect(result.imports).toEqual([]);
121
+ });
122
+ });
123
+ });
@@ -19,10 +19,10 @@ jest.mock("pg-boss", () => {
19
19
 
20
20
  describe("PgBossService", () => {
21
21
  let service: PgBossService;
22
- let mockPgBoss: any;
22
+ let mockPgBoss: jest.Mocked<PgBoss>;
23
23
 
24
24
  beforeEach(async () => {
25
- mockPgBoss = new PgBoss("connectionString");
25
+ mockPgBoss = new PgBoss("connectionString") as jest.Mocked<PgBoss>;
26
26
  mockPgBoss.createQueue.mockResolvedValue(undefined);
27
27
  mockPgBoss.send.mockResolvedValue("job-id");
28
28
  mockPgBoss.schedule.mockResolvedValue(undefined);
@@ -49,6 +49,7 @@ describe("PgBossService", () => {
49
49
 
50
50
  await service.registerJob("test-job", handler, options);
51
51
 
52
+ // eslint-disable-next-line @typescript-eslint/unbound-method
52
53
  expect(mockPgBoss.work).toHaveBeenCalledWith(
53
54
  "test-job",
54
55
  { includeMetadata: true },
@@ -63,8 +64,64 @@ describe("PgBossService", () => {
63
64
 
64
65
  await service.scheduleJob("test-job", data, {});
65
66
 
67
+ // eslint-disable-next-line @typescript-eslint/unbound-method
66
68
  expect(mockPgBoss.send).toHaveBeenCalledWith("test-job", data, {});
67
69
  });
70
+
71
+ it("should call createQueue before send", async () => {
72
+ const callOrder: string[] = [];
73
+ mockPgBoss.createQueue.mockImplementation(() => {
74
+ callOrder.push("createQueue");
75
+ return Promise.resolve();
76
+ });
77
+ mockPgBoss.send.mockImplementation(() => {
78
+ callOrder.push("send");
79
+ return Promise.resolve("job-id");
80
+ });
81
+
82
+ await service.scheduleJob("test-job", {});
83
+
84
+ expect(callOrder).toEqual(["createQueue", "send"]);
85
+ });
86
+ });
87
+
88
+ describe("scheduleCronJob", () => {
89
+ it("should call createQueue then schedule with correct params", async () => {
90
+ await service.scheduleCronJob(
91
+ "cron-job",
92
+ "0 * * * *",
93
+ { key: "val" },
94
+ { tz: "UTC" },
95
+ );
96
+
97
+ // eslint-disable-next-line @typescript-eslint/unbound-method
98
+ expect(mockPgBoss.createQueue).toHaveBeenCalledWith("cron-job");
99
+ // eslint-disable-next-line @typescript-eslint/unbound-method
100
+ expect(mockPgBoss.schedule).toHaveBeenCalledWith(
101
+ "cron-job",
102
+ "0 * * * *",
103
+ { key: "val" },
104
+ { tz: "UTC" },
105
+ );
106
+ });
107
+
108
+ it("should use {} defaults when data/options are omitted", async () => {
109
+ await service.scheduleCronJob("cron-job", "0 * * * *");
110
+
111
+ // eslint-disable-next-line @typescript-eslint/unbound-method
112
+ expect(mockPgBoss.schedule).toHaveBeenCalledWith(
113
+ "cron-job",
114
+ "0 * * * *",
115
+ {},
116
+ {},
117
+ );
118
+ });
119
+ });
120
+
121
+ describe("boss getter", () => {
122
+ it("should return the PgBoss instance", () => {
123
+ expect(service.boss).toBe(mockPgBoss);
124
+ });
68
125
  });
69
126
 
70
127
  describe("registerCronJob", () => {
@@ -82,12 +139,14 @@ describe("PgBossService", () => {
82
139
  options,
83
140
  );
84
141
 
142
+ // eslint-disable-next-line @typescript-eslint/unbound-method
85
143
  expect(mockPgBoss.schedule).toHaveBeenCalledWith(
86
144
  "test-cron-job",
87
145
  cron,
88
146
  data,
89
147
  { tz: "UTC" },
90
148
  );
149
+ // eslint-disable-next-line @typescript-eslint/unbound-method
91
150
  expect(mockPgBoss.work).toHaveBeenCalledWith(
92
151
  "test-cron-job",
93
152
  { includeMetadata: true, tz: "UTC" },
package/.eslintrc.js DELETED
@@ -1,33 +0,0 @@
1
- /** @type {import('eslint').ESLint.ConfigData} */
2
- module.exports = {
3
- parser: '@typescript-eslint/parser',
4
- parserOptions: {
5
- project: 'tsconfig.json',
6
- sourceType: 'module',
7
- },
8
- plugins: ['@typescript-eslint/eslint-plugin'],
9
- extends: [
10
- 'plugin:@typescript-eslint/recommended',
11
- 'plugin:prettier/recommended',
12
- ],
13
- root: true,
14
- env: {
15
- node: true,
16
- jest: true,
17
- },
18
- ignorePatterns: ['.eslintrc.js', 'tools/**'],
19
- rules: {
20
- '@typescript-eslint/interface-name-prefix': 'off',
21
- '@typescript-eslint/explicit-function-return-type': 'off',
22
- '@typescript-eslint/explicit-module-boundary-types': 'off',
23
- '@typescript-eslint/no-explicit-any': 'off',
24
- "@typescript-eslint/no-unused-vars": [
25
- "error",
26
- {
27
- "args": "all",
28
- "argsIgnorePattern": "^_",
29
- "varsIgnorePattern": "^_",
30
- }
31
- ],
32
- },
33
- };