From 2a158513028d9f862c4188b6957e78cd5663f26b Mon Sep 17 00:00:00 2001 From: Luca Chang <131398524+LucaButBoring@users.noreply.github.com> Date: Wed, 25 Mar 2026 08:57:53 -0700 Subject: [PATCH] [v1.x] fix: disallow null (infinite) requested TTL (#1339) Co-authored-by: Konstantin Konstantinov --- src/shared/protocol.ts | 2 +- src/types.ts | 5 ++-- .../tasks/stores/in-memory.test.ts | 11 ++++---- test/experimental/tasks/task.test.ts | 28 +++++++++++++++++++ 4 files changed, 36 insertions(+), 10 deletions(-) diff --git a/src/shared/protocol.ts b/src/shared/protocol.ts index 4ebfb5ef..2637be65 100644 --- a/src/shared/protocol.ts +++ b/src/shared/protocol.ts @@ -265,7 +265,7 @@ export type RequestHandlerExtra { expect(task).toBeNull(); }); - it('should support null TTL for unlimited lifetime', async () => { - // Test that null TTL means unlimited lifetime - const taskParams: TaskCreationParams = { - ttl: null - }; + it('should support omitted TTL for unlimited lifetime', async () => { + // Test that omitting TTL means unlimited lifetime (server returns null) + // Per spec: clients omit ttl to let server decide, server returns null for unlimited + const taskParams: TaskCreationParams = {}; const createdTask = await store.createTask(taskParams, 2222, { method: 'tools/call', params: {} }); - // The returned task should have null TTL + // The returned task should have null TTL (unlimited) expect(createdTask.ttl).toBeNull(); // Task should not be cleaned up even after a long time diff --git a/test/experimental/tasks/task.test.ts b/test/experimental/tasks/task.test.ts index 37e3938d..de613a32 100644 --- a/test/experimental/tasks/task.test.ts +++ b/test/experimental/tasks/task.test.ts @@ -1,6 +1,7 @@ import { describe, it, expect } from 'vitest'; import { isTerminal } from '../../../src/experimental/tasks/interfaces.js'; import type { Task } from '../../../src/types.js'; +import { TaskCreationParamsSchema } from '../../../src/types.js'; describe('Task utility functions', () => { describe('isTerminal', () => { @@ -115,3 +116,30 @@ describe('Task Schema Validation', () => { }); }); }); + +describe('TaskCreationParams Schema Validation', () => { + it('should accept ttl as a number', () => { + const result = TaskCreationParamsSchema.safeParse({ ttl: 60000 }); + expect(result.success).toBe(true); + }); + + it('should accept missing ttl (optional)', () => { + const result = TaskCreationParamsSchema.safeParse({}); + expect(result.success).toBe(true); + }); + + it('should reject null ttl (not allowed in request, only response)', () => { + const result = TaskCreationParamsSchema.safeParse({ ttl: null }); + expect(result.success).toBe(false); + }); + + it('should accept pollInterval as a number', () => { + const result = TaskCreationParamsSchema.safeParse({ pollInterval: 1000 }); + expect(result.success).toBe(true); + }); + + it('should accept both ttl and pollInterval', () => { + const result = TaskCreationParamsSchema.safeParse({ ttl: 60000, pollInterval: 1000 }); + expect(result.success).toBe(true); + }); +});