|
1 | | -import { deepStrictEqual, ok, strictEqual } from 'node:assert' |
| 1 | +import { deepStrictEqual, ok, strictEqual, doesNotThrow } from 'node:assert' |
2 | 2 | import { randomUUID } from 'node:crypto' |
3 | 3 | import { once } from 'node:events' |
4 | 4 | import { test, type TestContext } from 'node:test' |
@@ -3264,3 +3264,222 @@ test('metrics should track the number of active topics', async t => { |
3264 | 3264 | deepStrictEqual(activeTopics.values[0].value, 0) |
3265 | 3265 | } |
3266 | 3266 | }) |
| 3267 | + |
| 3268 | +test('pause should prevent fetches from paused partitions during consumption', async t => { |
| 3269 | + const topic = await createTopic(t, true, 2) |
| 3270 | + const messages = Array.from({ length: 10 }, (_, i) => ({ |
| 3271 | + topic, |
| 3272 | + key: `key-${i}`, |
| 3273 | + value: `value-${i}`, |
| 3274 | + partition: i % 2 |
| 3275 | + })) |
| 3276 | + |
| 3277 | + await produceTestMessages({ t, messages }) |
| 3278 | + |
| 3279 | + const consumer = createConsumer(t) |
| 3280 | + const stream = await consumer.consume({ |
| 3281 | + topics: [topic], |
| 3282 | + mode: MessagesStreamModes.EARLIEST, |
| 3283 | + autocommit: true |
| 3284 | + }) |
| 3285 | + |
| 3286 | + consumer.pause([{ topic, partition: 0 }]) |
| 3287 | + |
| 3288 | + const received: number[] = [] |
| 3289 | + let count = 0 |
| 3290 | + |
| 3291 | + for await (const message of stream) { |
| 3292 | + received.push(message.partition) |
| 3293 | + count++ |
| 3294 | + |
| 3295 | + if (count === 5) { |
| 3296 | + break |
| 3297 | + } |
| 3298 | + } |
| 3299 | + |
| 3300 | + strictEqual( |
| 3301 | + received.every(p => p === 1), |
| 3302 | + true |
| 3303 | + ) |
| 3304 | +}) |
| 3305 | + |
| 3306 | +test('resume should allow fetches from previously paused partitions', async t => { |
| 3307 | + const topic = await createTopic(t, true, 2) |
| 3308 | + const messages = Array.from({ length: 10 }, (_, i) => ({ |
| 3309 | + topic, |
| 3310 | + key: `key-${i}`, |
| 3311 | + value: `value-${i}` |
| 3312 | + })) |
| 3313 | + |
| 3314 | + await produceTestMessages({ t, messages: messages.slice(0, 5), overrideOptions: { partitioner: () => 0 } }) |
| 3315 | + await produceTestMessages({ t, messages: messages.slice(5, 10), overrideOptions: { partitioner: () => 1 } }) |
| 3316 | + |
| 3317 | + const consumer = createConsumer(t) |
| 3318 | + const stream = await consumer.consume({ |
| 3319 | + topics: [topic], |
| 3320 | + mode: MessagesStreamModes.EARLIEST, |
| 3321 | + autocommit: true, |
| 3322 | + maxWaitTime: 100 |
| 3323 | + }) |
| 3324 | + |
| 3325 | + consumer.pause([{ topic, partition: 0 }]) |
| 3326 | + |
| 3327 | + const received: { key: string; partition: number }[] = [] |
| 3328 | + let count = 0 |
| 3329 | + |
| 3330 | + for await (const message of stream) { |
| 3331 | + received.push({ key: message.key.toString(), partition: message.partition }) |
| 3332 | + count++ |
| 3333 | + |
| 3334 | + if (count === 5) { |
| 3335 | + consumer.resume([{ topic, partition: 0 }]) |
| 3336 | + } |
| 3337 | + |
| 3338 | + if (count === 10) { |
| 3339 | + break |
| 3340 | + } |
| 3341 | + } |
| 3342 | + |
| 3343 | + strictEqual( |
| 3344 | + received.slice(0, 5).every(m => m.partition === 1), |
| 3345 | + true |
| 3346 | + ) |
| 3347 | + strictEqual( |
| 3348 | + received.slice(5, 10).every(m => m.partition === 0), |
| 3349 | + true |
| 3350 | + ) |
| 3351 | +}) |
| 3352 | + |
| 3353 | +test('resume should handle resuming non-paused partitions gracefully', async t => { |
| 3354 | + const topic = await createTopic(t, true) |
| 3355 | + const consumer = createConsumer(t) |
| 3356 | + |
| 3357 | + consumer.topics.trackAll(topic) |
| 3358 | + await consumer.joinGroup({}) |
| 3359 | + |
| 3360 | + doesNotThrow(() => consumer.resume([{ topic, partition: 0 }])) |
| 3361 | +}) |
| 3362 | + |
| 3363 | +test('pause/resume should throw error if consumer has not joined a group', async t => { |
| 3364 | + const topic = await createTopic(t, true) |
| 3365 | + const consumer = createConsumer(t) |
| 3366 | + |
| 3367 | + try { |
| 3368 | + consumer.pause([{ topic, partition: 0 }]) |
| 3369 | + throw new Error('Expected error not thrown') |
| 3370 | + } catch (error) { |
| 3371 | + strictEqual(error instanceof UserError, true) |
| 3372 | + strictEqual(error.message, 'Cannot pause partitions before joining a consumer group.') |
| 3373 | + } |
| 3374 | + |
| 3375 | + try { |
| 3376 | + consumer.resume([{ topic, partition: 0 }]) |
| 3377 | + throw new Error('Expected error not thrown') |
| 3378 | + } catch (error) { |
| 3379 | + strictEqual(error instanceof UserError, true) |
| 3380 | + strictEqual(error.message, 'Cannot resume partitions before joining a consumer group.') |
| 3381 | + } |
| 3382 | +}) |
| 3383 | + |
| 3384 | +test('pause/resume should throw error if topic is not assigned to consumer', async t => { |
| 3385 | + const topic1 = await createTopic(t, true) |
| 3386 | + const topic2 = await createTopic(t, true) |
| 3387 | + const consumer = createConsumer(t) |
| 3388 | + |
| 3389 | + consumer.topics.trackAll(topic1) |
| 3390 | + await consumer.joinGroup({}) |
| 3391 | + |
| 3392 | + try { |
| 3393 | + consumer.pause([{ topic: topic2, partition: 0 }]) |
| 3394 | + throw new Error('Expected error not thrown') |
| 3395 | + } catch (error) { |
| 3396 | + strictEqual(error instanceof UserError, true) |
| 3397 | + strictEqual(error.message, `Topic '${topic2}' is not assigned to this consumer.`) |
| 3398 | + } |
| 3399 | + |
| 3400 | + try { |
| 3401 | + consumer.resume([{ topic: topic2, partition: 0 }]) |
| 3402 | + throw new Error('Expected error not thrown') |
| 3403 | + } catch (error) { |
| 3404 | + strictEqual(error instanceof UserError, true) |
| 3405 | + strictEqual(error.message, `Topic '${topic2}' is not assigned to this consumer.`) |
| 3406 | + } |
| 3407 | +}) |
| 3408 | + |
| 3409 | +test('pause/resume should handle multiple topic-partitions', async t => { |
| 3410 | + const topic1 = await createTopic(t, true) |
| 3411 | + const topic2 = await createTopic(t, true) |
| 3412 | + const consumer = createConsumer(t) |
| 3413 | + |
| 3414 | + consumer.topics.trackAll(topic1, topic2) |
| 3415 | + await consumer.joinGroup({}) |
| 3416 | + |
| 3417 | + consumer.pause([ |
| 3418 | + { topic: topic1, partition: 0 }, |
| 3419 | + { topic: topic2, partition: 0 } |
| 3420 | + ]) |
| 3421 | + |
| 3422 | + strictEqual(consumer.isPaused({ topic: topic1, partition: 0 }), true) |
| 3423 | + strictEqual(consumer.isPaused({ topic: topic2, partition: 0 }), true) |
| 3424 | + |
| 3425 | + consumer.resume([ |
| 3426 | + { topic: topic1, partition: 0 }, |
| 3427 | + { topic: topic2, partition: 0 } |
| 3428 | + ]) |
| 3429 | + |
| 3430 | + strictEqual(consumer.isPaused({ topic: topic1, partition: 0 }), false) |
| 3431 | + strictEqual(consumer.isPaused({ topic: topic2, partition: 0 }), false) |
| 3432 | +}) |
| 3433 | + |
| 3434 | +test('paused should return all paused topic-partitions', async t => { |
| 3435 | + const topic1 = await createTopic(t, true, 2) |
| 3436 | + const topic2 = await createTopic(t, true, 2) |
| 3437 | + const consumer = createConsumer(t) |
| 3438 | + |
| 3439 | + consumer.topics.trackAll(topic1, topic2) |
| 3440 | + await consumer.joinGroup({}) |
| 3441 | + |
| 3442 | + consumer.pause([ |
| 3443 | + { topic: topic1, partition: 0 }, |
| 3444 | + { topic: topic1, partition: 1 }, |
| 3445 | + { topic: topic2, partition: 0 } |
| 3446 | + ]) |
| 3447 | + |
| 3448 | + deepStrictEqual(consumer.paused(), [ |
| 3449 | + { topic: topic1, partition: 0 }, |
| 3450 | + { topic: topic1, partition: 1 }, |
| 3451 | + { topic: topic2, partition: 0 } |
| 3452 | + ]) |
| 3453 | +}) |
| 3454 | + |
| 3455 | +test('isPaused should return true for paused topic-partitions', async t => { |
| 3456 | + const topic = await createTopic(t, true) |
| 3457 | + const consumer = createConsumer(t) |
| 3458 | + |
| 3459 | + consumer.topics.trackAll(topic) |
| 3460 | + await consumer.joinGroup({}) |
| 3461 | + |
| 3462 | + consumer.pause([{ topic, partition: 0 }]) |
| 3463 | + strictEqual(consumer.isPaused({ topic, partition: 0 }), true) |
| 3464 | +}) |
| 3465 | + |
| 3466 | +test('isPaused should return false for non-paused topic-partitions', async t => { |
| 3467 | + const topic = await createTopic(t, true) |
| 3468 | + const consumer = createConsumer(t) |
| 3469 | + |
| 3470 | + consumer.topics.trackAll(topic) |
| 3471 | + await consumer.joinGroup({}) |
| 3472 | + |
| 3473 | + strictEqual(consumer.isPaused({ topic, partition: 0 }), false) |
| 3474 | +}) |
| 3475 | + |
| 3476 | +test('isPaused should return false for topic-partitions not assigned to consumer', async t => { |
| 3477 | + const topic1 = await createTopic(t, true) |
| 3478 | + const topic2 = await createTopic(t, true) |
| 3479 | + const consumer = createConsumer(t) |
| 3480 | + |
| 3481 | + consumer.topics.trackAll(topic1) |
| 3482 | + await consumer.joinGroup({}) |
| 3483 | + |
| 3484 | + strictEqual(consumer.isPaused({ topic: topic2, partition: 0 }), false) |
| 3485 | +}) |
0 commit comments