@tstdl/base 0.93.140 → 0.93.142
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/application/application.d.ts +1 -1
- package/application/application.js +1 -1
- package/application/providers.d.ts +20 -2
- package/application/providers.js +34 -7
- package/audit/module.d.ts +5 -0
- package/audit/module.js +9 -1
- package/authentication/client/authentication.service.d.ts +1 -0
- package/authentication/client/authentication.service.js +3 -2
- package/authentication/server/module.d.ts +5 -0
- package/authentication/server/module.js +9 -1
- package/authentication/tests/authentication.api-controller.test.js +1 -1
- package/authentication/tests/authentication.api-request-token.provider.test.js +1 -1
- package/authentication/tests/authentication.client-service.test.js +1 -1
- package/circuit-breaker/circuit-breaker.d.ts +6 -4
- package/circuit-breaker/postgres/circuit-breaker.d.ts +1 -0
- package/circuit-breaker/postgres/circuit-breaker.js +8 -5
- package/circuit-breaker/postgres/module.d.ts +1 -0
- package/circuit-breaker/postgres/module.js +5 -1
- package/circuit-breaker/tests/circuit-breaker.test.js +20 -0
- package/document-management/server/configure.js +5 -1
- package/document-management/server/module.d.ts +1 -1
- package/document-management/server/module.js +1 -1
- package/document-management/server/services/document-management-ancillary.service.js +1 -1
- package/document-management/tests/ai-config-hierarchy.test.js +0 -5
- package/document-management/tests/document-management-ai-overrides.test.js +0 -1
- package/document-management/tests/document-validation-ai-overrides.test.js +0 -1
- package/examples/document-management/main.d.ts +1 -0
- package/examples/document-management/main.js +14 -11
- package/key-value-store/postgres/module.d.ts +1 -0
- package/key-value-store/postgres/module.js +5 -1
- package/lock/postgres/module.d.ts +1 -0
- package/lock/postgres/module.js +5 -1
- package/mail/module.d.ts +5 -1
- package/mail/module.js +11 -6
- package/module/modules/web-server.module.js +2 -3
- package/notification/server/module.d.ts +1 -0
- package/notification/server/module.js +5 -1
- package/notification/tests/notification-api.test.js +5 -1
- package/notification/tests/notification-flow.test.js +8 -5
- package/orm/decorators.d.ts +22 -5
- package/orm/decorators.js +10 -1
- package/orm/server/bootstrap.d.ts +11 -0
- package/orm/server/bootstrap.js +31 -0
- package/orm/server/drizzle/schema-converter.d.ts +3 -1
- package/orm/server/drizzle/schema-converter.js +85 -56
- package/orm/server/encryption.d.ts +0 -1
- package/orm/server/encryption.js +1 -4
- package/orm/server/extension.d.ts +14 -0
- package/orm/server/extension.js +27 -0
- package/orm/server/index.d.ts +3 -6
- package/orm/server/index.js +3 -6
- package/orm/server/migration.d.ts +18 -0
- package/orm/server/migration.js +58 -0
- package/orm/server/repository.d.ts +2 -1
- package/orm/server/repository.js +19 -9
- package/orm/server/transaction.d.ts +6 -10
- package/orm/server/transaction.js +25 -26
- package/orm/server/transactional.js +3 -3
- package/orm/tests/database-extension.test.js +63 -0
- package/orm/tests/database-migration.test.js +83 -0
- package/orm/tests/encryption.test.js +3 -4
- package/orm/tests/repository-compound-primary-key.test.d.ts +2 -0
- package/orm/tests/repository-compound-primary-key.test.js +234 -0
- package/orm/tests/schema-generation.test.d.ts +1 -0
- package/orm/tests/schema-generation.test.js +52 -5
- package/orm/utils.d.ts +17 -2
- package/orm/utils.js +49 -1
- package/package.json +5 -4
- package/rate-limit/postgres/module.d.ts +1 -0
- package/rate-limit/postgres/module.js +5 -1
- package/reflection/decorator-data.js +11 -12
- package/task-queue/README.md +2 -10
- package/task-queue/postgres/drizzle/0000_great_gwen_stacy.sql +84 -0
- package/task-queue/postgres/drizzle/meta/0000_snapshot.json +250 -89
- package/task-queue/postgres/drizzle/meta/_journal.json +2 -2
- package/task-queue/postgres/module.d.ts +1 -0
- package/task-queue/postgres/module.js +6 -1
- package/task-queue/postgres/schemas.d.ts +15 -6
- package/task-queue/postgres/schemas.js +4 -3
- package/task-queue/postgres/task-queue.d.ts +18 -15
- package/task-queue/postgres/task-queue.js +797 -499
- package/task-queue/postgres/task.model.d.ts +20 -9
- package/task-queue/postgres/task.model.js +65 -39
- package/task-queue/task-context.d.ts +12 -7
- package/task-queue/task-context.js +8 -6
- package/task-queue/task-queue.d.ts +364 -43
- package/task-queue/task-queue.js +153 -41
- package/task-queue/tests/coverage-branch.test.d.ts +1 -0
- package/task-queue/tests/coverage-branch.test.js +395 -0
- package/task-queue/tests/coverage-enhancement.test.d.ts +1 -0
- package/task-queue/tests/coverage-enhancement.test.js +150 -0
- package/task-queue/tests/dag.test.d.ts +1 -0
- package/task-queue/tests/dag.test.js +188 -0
- package/task-queue/tests/dependencies.test.js +165 -47
- package/task-queue/tests/enqueue-batch.test.d.ts +1 -0
- package/task-queue/tests/enqueue-batch.test.js +125 -0
- package/task-queue/tests/fan-out-spawning.test.d.ts +1 -0
- package/task-queue/tests/fan-out-spawning.test.js +94 -0
- package/task-queue/tests/idempotent-replacement.test.d.ts +1 -0
- package/task-queue/tests/idempotent-replacement.test.js +114 -0
- package/task-queue/tests/missing-idempotent-tasks.test.d.ts +1 -0
- package/task-queue/tests/missing-idempotent-tasks.test.js +39 -0
- package/task-queue/tests/queue.test.js +294 -49
- package/task-queue/tests/shutdown.test.d.ts +1 -0
- package/task-queue/tests/shutdown.test.js +41 -0
- package/task-queue/tests/transactions.test.d.ts +1 -0
- package/task-queue/tests/transactions.test.js +47 -0
- package/task-queue/tests/worker.test.js +63 -15
- package/task-queue/tests/zombie-parent.test.d.ts +1 -0
- package/task-queue/tests/zombie-parent.test.js +45 -0
- package/task-queue/tests/zombie-recovery.test.d.ts +1 -0
- package/task-queue/tests/zombie-recovery.test.js +51 -0
- package/test5.js +5 -5
- package/testing/integration-setup.d.ts +4 -4
- package/testing/integration-setup.js +56 -29
- package/text/localization.service.js +2 -2
- package/utils/file-reader.js +1 -2
- package/utils/timing.d.ts +2 -2
- package/task-queue/postgres/drizzle/0000_simple_invisible_woman.sql +0 -74
- package/task-queue/tests/complex.test.js +0 -306
- package/task-queue/tests/extensive-dependencies.test.js +0 -234
- /package/{task-queue/tests/complex.test.d.ts → orm/tests/database-extension.test.d.ts} +0 -0
- /package/{task-queue/tests/extensive-dependencies.test.d.ts → orm/tests/database-migration.test.d.ts} +0 -0
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it } from 'vitest';
|
|
2
|
+
import { Database } from '../../orm/server/index.js';
|
|
3
|
+
import { TaskQueueProvider, TaskStatus } from '../../task-queue/index.js';
|
|
4
|
+
import { task as taskTable } from '../../task-queue/postgres/schemas.js';
|
|
5
|
+
import { setupIntegrationTest } from '../../testing/index.js';
|
|
6
|
+
import { currentTimestamp } from '../../utils/date-time.js';
|
|
7
|
+
import { eq } from 'drizzle-orm';
|
|
8
|
+
describe('Fan-Out Spawning', () => {
|
|
9
|
+
let injector;
|
|
10
|
+
let queue;
|
|
11
|
+
let database;
|
|
12
|
+
beforeAll(async () => {
|
|
13
|
+
({ injector } = await setupIntegrationTest({ modules: { taskQueue: true } }));
|
|
14
|
+
database = injector.resolve(Database);
|
|
15
|
+
});
|
|
16
|
+
beforeEach(() => {
|
|
17
|
+
const queueProvider = injector.resolve(TaskQueueProvider);
|
|
18
|
+
const queueName = `fan-out-queue-${crypto.randomUUID()}`;
|
|
19
|
+
queue = queueProvider.get(queueName, {
|
|
20
|
+
visibilityTimeout: 1000,
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
afterEach(async () => {
|
|
24
|
+
await queue.clear();
|
|
25
|
+
});
|
|
26
|
+
afterAll(async () => {
|
|
27
|
+
await injector?.dispose();
|
|
28
|
+
});
|
|
29
|
+
it('should transition parent to Waiting when spawning children during execution', async () => {
|
|
30
|
+
const parent = await queue.enqueue('parent', {});
|
|
31
|
+
const dParent = await queue.dequeue();
|
|
32
|
+
expect(dParent?.id).toBe(parent.id);
|
|
33
|
+
// Parent spawns a child
|
|
34
|
+
const [child] = await queue.enqueueMany([{ type: 'child', data: {}, parentId: parent.id }], { returnTasks: true });
|
|
35
|
+
// Complete parent
|
|
36
|
+
await queue.complete(dParent);
|
|
37
|
+
// Parent should be Waiting for child
|
|
38
|
+
const uParent = await queue.getTask(parent.id);
|
|
39
|
+
expect(uParent?.status).toBe(TaskStatus.WaitingChildren);
|
|
40
|
+
expect(uParent?.unresolvedCompleteDependencies).toBe(1);
|
|
41
|
+
// Complete child
|
|
42
|
+
const dChild = await queue.dequeue();
|
|
43
|
+
await queue.complete(dChild);
|
|
44
|
+
// Parent should now be Completed
|
|
45
|
+
const fParent = await queue.getTask(parent.id);
|
|
46
|
+
expect(fParent?.status).toBe(TaskStatus.Completed);
|
|
47
|
+
});
|
|
48
|
+
it('should NOT transition parent to Waiting if waitForCompletion is false', async () => {
|
|
49
|
+
const parent = await queue.enqueue('parent', {});
|
|
50
|
+
const dParent = await queue.dequeue();
|
|
51
|
+
// Spawn child with waitForCompletion: false
|
|
52
|
+
await queue.enqueueMany([{ type: 'child', data: {}, parentId: parent.id, waitForCompletion: false }], { returnTasks: true });
|
|
53
|
+
await queue.complete(dParent);
|
|
54
|
+
const uParent = await queue.getTask(parent.id);
|
|
55
|
+
expect(uParent?.status).toBe(TaskStatus.Completed); // Finished immediately
|
|
56
|
+
expect(uParent?.unresolvedCompleteDependencies).toBe(0);
|
|
57
|
+
});
|
|
58
|
+
it('should transition parent from WaitingChildren to Completed exactly when the last child finishes', async () => {
|
|
59
|
+
const parent = await queue.enqueue('parent', {});
|
|
60
|
+
const c1 = await queue.enqueue('child', { val: 1 }, { parentId: parent.id });
|
|
61
|
+
const c2 = await queue.enqueue('child', { val: 2 }, { parentId: parent.id });
|
|
62
|
+
const dp = await queue.dequeue();
|
|
63
|
+
await queue.complete(dp);
|
|
64
|
+
const up1 = await queue.getTask(parent.id);
|
|
65
|
+
expect(up1?.status).toBe(TaskStatus.WaitingChildren);
|
|
66
|
+
const dc1 = await queue.dequeue();
|
|
67
|
+
await queue.complete(dc1);
|
|
68
|
+
const up2 = await queue.getTask(parent.id);
|
|
69
|
+
expect(up2?.status).toBe(TaskStatus.WaitingChildren);
|
|
70
|
+
const dc2 = await queue.dequeue();
|
|
71
|
+
await queue.complete(dc2);
|
|
72
|
+
const up3 = await queue.getTask(parent.id);
|
|
73
|
+
expect(up3?.status).toBe(TaskStatus.Completed);
|
|
74
|
+
});
|
|
75
|
+
it('should protect parent tasks from archival if orphaned child tasks still exist', async () => {
|
|
76
|
+
const queueProvider = injector.resolve(TaskQueueProvider);
|
|
77
|
+
const ephemeralQueue = queueProvider.get(`ephemeral-${crypto.randomUUID()}`, { retention: 0 });
|
|
78
|
+
const parent = await ephemeralQueue.enqueue('parent', {});
|
|
79
|
+
await ephemeralQueue.enqueue('child', {}, { parentId: parent.id });
|
|
80
|
+
// Manually complete parent while child is still pending
|
|
81
|
+
await database
|
|
82
|
+
.update(taskTable)
|
|
83
|
+
.set({
|
|
84
|
+
status: TaskStatus.Completed,
|
|
85
|
+
completeTimestamp: currentTimestamp() - 1000,
|
|
86
|
+
})
|
|
87
|
+
.where(eq(taskTable.id, parent.id));
|
|
88
|
+
await ephemeralQueue.maintenance();
|
|
89
|
+
const updatedParent = await ephemeralQueue.getTask(parent.id);
|
|
90
|
+
expect(updatedParent).toBeDefined(); // Should still be in main table
|
|
91
|
+
expect(updatedParent?.status).toBe(TaskStatus.Completed);
|
|
92
|
+
await ephemeralQueue.clear();
|
|
93
|
+
});
|
|
94
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { eq } from 'drizzle-orm';
|
|
2
|
+
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it } from 'vitest';
|
|
3
|
+
import { Database } from '../../orm/server/index.js';
|
|
4
|
+
import { TaskQueueProvider, TaskStatus } from '../../task-queue/index.js';
|
|
5
|
+
import { taskDependency as taskDependencyTable } from '../../task-queue/postgres/schemas.js';
|
|
6
|
+
import { setupIntegrationTest } from '../../testing/index.js';
|
|
7
|
+
import { timeout } from '../../utils/timing.js';
|
|
8
|
+
describe('Idempotent Replacement', () => {
|
|
9
|
+
let injector;
|
|
10
|
+
let queue;
|
|
11
|
+
let database;
|
|
12
|
+
beforeAll(async () => {
|
|
13
|
+
({ injector } = await setupIntegrationTest({ modules: { taskQueue: true } }));
|
|
14
|
+
database = injector.resolve(Database);
|
|
15
|
+
});
|
|
16
|
+
beforeEach(() => {
|
|
17
|
+
const queueProvider = injector.resolve(TaskQueueProvider);
|
|
18
|
+
const queueName = `idempotent-queue-${crypto.randomUUID()}`;
|
|
19
|
+
queue = queueProvider.get(queueName, {
|
|
20
|
+
visibilityTimeout: 1000,
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
afterEach(async () => {
|
|
24
|
+
await queue.clear();
|
|
25
|
+
});
|
|
26
|
+
afterAll(async () => {
|
|
27
|
+
await injector?.dispose();
|
|
28
|
+
});
|
|
29
|
+
it('should NOT be affected by "ghost" dependencies after replacement', async () => {
|
|
30
|
+
const depA = await queue.enqueue('depA', {});
|
|
31
|
+
const depB = await queue.enqueue('depB', {});
|
|
32
|
+
const depC = await queue.enqueue('depC', {});
|
|
33
|
+
const idempotencyKey = 'my-idempotent-task';
|
|
34
|
+
// 1. Initial enqueue with A and B
|
|
35
|
+
const taskV1 = await queue.enqueue('main', { version: 1 }, {
|
|
36
|
+
idempotencyKey,
|
|
37
|
+
scheduleAfter: [depA.id, depB.id],
|
|
38
|
+
});
|
|
39
|
+
expect(taskV1.status).toBe(TaskStatus.Waiting);
|
|
40
|
+
expect(taskV1.unresolvedScheduleDependencies).toBe(2);
|
|
41
|
+
// 2. Replacement enqueue with only C
|
|
42
|
+
const taskV2 = await queue.enqueue('main', { version: 2 }, {
|
|
43
|
+
idempotencyKey,
|
|
44
|
+
scheduleAfter: [depC.id],
|
|
45
|
+
replace: true,
|
|
46
|
+
});
|
|
47
|
+
expect(taskV2.id).toBe(taskV1.id);
|
|
48
|
+
expect(taskV2.unresolvedScheduleDependencies).toBe(1);
|
|
49
|
+
expect(taskV2.data).toEqual({ version: 2 });
|
|
50
|
+
// 3. Complete an OLD dependency (depA)
|
|
51
|
+
const dDepA = await queue.dequeue({ types: ['depA'] });
|
|
52
|
+
await queue.complete(dDepA);
|
|
53
|
+
// Give it a moment to process background dependency resolution if any
|
|
54
|
+
await timeout(100);
|
|
55
|
+
// 4. Verify main task is STILL waiting for depC
|
|
56
|
+
const uTask = await queue.getTask(taskV1.id);
|
|
57
|
+
expect(uTask?.status).toBe(TaskStatus.Waiting);
|
|
58
|
+
expect(uTask?.unresolvedScheduleDependencies).toBe(1);
|
|
59
|
+
// 5. Complete NEW dependency (depC)
|
|
60
|
+
const dDepC = await queue.dequeue({ types: ['depC'] });
|
|
61
|
+
await queue.complete(dDepC);
|
|
62
|
+
// 6. Verify main task is now Pending
|
|
63
|
+
const fTask = await queue.getTask(taskV1.id);
|
|
64
|
+
expect(fTask?.status).toBe(TaskStatus.Pending);
|
|
65
|
+
});
|
|
66
|
+
it('should update parentId on replacement', async () => {
|
|
67
|
+
const parent1 = await queue.enqueue('parent', { id: 1 });
|
|
68
|
+
const parent2 = await queue.enqueue('parent', { id: 2 });
|
|
69
|
+
const idempotencyKey = 'task-1';
|
|
70
|
+
const taskV1 = await queue.enqueue('child', {}, { idempotencyKey, parentId: parent1.id });
|
|
71
|
+
expect(taskV1.parentId).toBe(parent1.id);
|
|
72
|
+
// Replace with new parent
|
|
73
|
+
const taskV2 = await queue.enqueue('child', {}, { idempotencyKey, parentId: parent2.id, replace: true });
|
|
74
|
+
expect(taskV2.id).toBe(taskV1.id);
|
|
75
|
+
expect(taskV2.parentId).toBe(parent2.id);
|
|
76
|
+
});
|
|
77
|
+
it('should NOT corrupt state on idempotent enqueues (replace: false)', async () => {
|
|
78
|
+
const dep = await queue.enqueue('dep', {});
|
|
79
|
+
const idempotencyKey = 'task-2';
|
|
80
|
+
// Initial enqueue without dependency
|
|
81
|
+
const taskV1 = await queue.enqueue('main', {}, { idempotencyKey });
|
|
82
|
+
expect(taskV1.status).toBe(TaskStatus.Pending);
|
|
83
|
+
// Deduplicated enqueue with dependency
|
|
84
|
+
const taskV2 = await queue.enqueue('main', {}, { idempotencyKey, scheduleAfter: [dep.id], replace: false });
|
|
85
|
+
expect(taskV2.id).toBe(taskV1.id);
|
|
86
|
+
const task = await queue.getTask(taskV2.id);
|
|
87
|
+
expect(task?.status).toBe(TaskStatus.Pending);
|
|
88
|
+
expect(task?.unresolvedScheduleDependencies).toBe(0);
|
|
89
|
+
});
|
|
90
|
+
it('should NOT orphan children on task replacement', async () => {
|
|
91
|
+
const idempotencyKey = 'parent-task';
|
|
92
|
+
const parent = await queue.enqueue('parent', {}, { idempotencyKey });
|
|
93
|
+
// Enqueue child
|
|
94
|
+
await queue.enqueue('child', {}, { parentId: parent.id });
|
|
95
|
+
const parentAfterChild = await queue.getTask(parent.id);
|
|
96
|
+
expect(parentAfterChild?.unresolvedCompleteDependencies).toBe(1);
|
|
97
|
+
// Replace parent
|
|
98
|
+
const replacedParent = await queue.enqueue('parent', { replaced: true }, { idempotencyKey, replace: true });
|
|
99
|
+
expect(replacedParent.unresolvedCompleteDependencies).toBe(1);
|
|
100
|
+
});
|
|
101
|
+
it('should clear and recreate dependencies when enqueuing existing tasks with replace: true', async () => {
|
|
102
|
+
const dep1 = await queue.enqueue('test', { value: 'dep1' });
|
|
103
|
+
const dep2 = await queue.enqueue('test', { value: 'dep2' });
|
|
104
|
+
const task = await queue.enqueue('test', { value: 'main' }, { idempotencyKey: 'main-key', scheduleAfter: [dep1.id] });
|
|
105
|
+
// Re-enqueue with replace: true and different dependency
|
|
106
|
+
await queue.enqueue('test', { value: 'main-replaced' }, { idempotencyKey: 'main-key', replace: true, scheduleAfter: [dep2.id] });
|
|
107
|
+
const deps = await database
|
|
108
|
+
.select()
|
|
109
|
+
.from(taskDependencyTable)
|
|
110
|
+
.where(eq(taskDependencyTable.taskId, task.id));
|
|
111
|
+
expect(deps).toHaveLength(1);
|
|
112
|
+
expect(deps[0].dependencyTaskId).toBe(dep2.id);
|
|
113
|
+
});
|
|
114
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it } from 'vitest';
|
|
2
|
+
import { TaskQueueProvider, TaskStatus } from '../../task-queue/index.js';
|
|
3
|
+
import { setupIntegrationTest } from '../../testing/index.js';
|
|
4
|
+
describe('Missing Idempotent Tasks Bug', () => {
|
|
5
|
+
let injector;
|
|
6
|
+
let queue;
|
|
7
|
+
beforeAll(async () => {
|
|
8
|
+
({ injector } = await setupIntegrationTest({ modules: { taskQueue: true } }));
|
|
9
|
+
});
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
const queueProvider = injector.resolve(TaskQueueProvider);
|
|
12
|
+
const queueName = `missing-idem-queue-${crypto.randomUUID()}`;
|
|
13
|
+
queue = queueProvider.get(queueName, {
|
|
14
|
+
visibilityTimeout: 1000,
|
|
15
|
+
});
|
|
16
|
+
});
|
|
17
|
+
afterEach(async () => {
|
|
18
|
+
await queue.clear();
|
|
19
|
+
});
|
|
20
|
+
afterAll(async () => {
|
|
21
|
+
await injector?.dispose();
|
|
22
|
+
});
|
|
23
|
+
it('should NOT crash when enqueuing duplicate idempotent tasks with dependencies and returnTasks: false', async () => {
|
|
24
|
+
const key = 'idem-key';
|
|
25
|
+
const dep = await queue.enqueue('dep', {});
|
|
26
|
+
// 1. Initial enqueue
|
|
27
|
+
const task1 = await queue.enqueue('main', {}, { idempotencyKey: key });
|
|
28
|
+
// 2. Second enqueue with same key and a dependency, returnTasks: false
|
|
29
|
+
// This should NOT crash.
|
|
30
|
+
// replace=false means we should ignore the new data AND the new dependency for the existing task.
|
|
31
|
+
await expect(queue.enqueueMany([
|
|
32
|
+
{ type: 'main', data: { new: 'data' }, idempotencyKey: key, scheduleAfter: [dep.id] }
|
|
33
|
+
], { replace: false, returnTasks: false })).resolves.toBeUndefined();
|
|
34
|
+
const updatedTask = await queue.getTask(task1.id);
|
|
35
|
+
expect(updatedTask?.data).toEqual({}); // Data should not have changed
|
|
36
|
+
expect(updatedTask?.unresolvedScheduleDependencies).toBe(0); // Dependency should not have been added
|
|
37
|
+
expect(updatedTask?.status).toBe(TaskStatus.Pending); // Should still be Pending, not Waiting
|
|
38
|
+
});
|
|
39
|
+
});
|