|
2 | 2 | // SPDX-License-Identifier: Apache-2.0 |
3 | 3 | import { describe, it } from 'node:test'; |
4 | 4 | import assert from 'node:assert/strict'; |
5 | | -import { deleteDrainSet } from './kv_keys_handler.js'; |
| 5 | +import { batches, computeDiff, deleteDrainSet } from './kv_keys_handler.js'; |
6 | 6 |
|
7 | 7 | // Regression for the Delete-path drain bug: CloudFormation does not send |
8 | 8 | // OldResourceProperties on Delete, so the keys to drain must come from |
@@ -40,3 +40,83 @@ describe('kv_keys_handler — deleteDrainSet', () => { |
40 | 40 | assert.deepEqual(deleteDrainSet(event), real); |
41 | 41 | }); |
42 | 42 | }); |
| 43 | + |
| 44 | +// The route-table flip is applied via batched UpdateKeys calls. An off-by-one |
| 45 | +// at the 50-key boundary would partial-apply the table mid-cutover and surface |
| 46 | +// as an opaque deploy-time failure — so the pure diff + batching are unit-tested |
| 47 | +// at the boundaries here. |
| 48 | +describe('kv_keys_handler — computeDiff', () => { |
| 49 | + it('puts new + changed keys, deletes removed keys, skips unchanged', () => { |
| 50 | + const desired = { a: '1', b: '2-new', c: '3' }; // a unchanged, b changed, c new |
| 51 | + const previous = { a: '1', b: '2-old', d: '4' }; // d removed |
| 52 | + const { puts, deletes } = computeDiff(desired, previous); |
| 53 | + assert.deepEqual( |
| 54 | + puts.sort((x, y) => x.Key.localeCompare(y.Key)), |
| 55 | + [ |
| 56 | + { Key: 'b', Value: '2-new' }, |
| 57 | + { Key: 'c', Value: '3' }, |
| 58 | + ], |
| 59 | + ); |
| 60 | + assert.deepEqual(deletes, [{ Key: 'd' }]); |
| 61 | + }); |
| 62 | + |
| 63 | + it('is a no-op when desired equals previous', () => { |
| 64 | + const same = { a: '1', b: '2' }; |
| 65 | + const { puts, deletes } = computeDiff(same, { ...same }); |
| 66 | + assert.equal(puts.length, 0); |
| 67 | + assert.equal(deletes.length, 0); |
| 68 | + }); |
| 69 | +}); |
| 70 | + |
| 71 | +describe('kv_keys_handler — batches (50-key / 3 MB boundaries)', () => { |
| 72 | + const mkPuts = (n: number) => |
| 73 | + Array.from({ length: n }, (_, i) => ({ Key: `k${i}`, Value: 'v' })); |
| 74 | + const collect = ( |
| 75 | + puts: { Key: string; Value: string }[], |
| 76 | + deletes: { Key: string }[] = [], |
| 77 | + ) => [...batches(puts, deletes)]; |
| 78 | + |
| 79 | + it('packs exactly 50 puts into a single batch', () => { |
| 80 | + const out = collect(mkPuts(50)); |
| 81 | + assert.equal(out.length, 1); |
| 82 | + assert.equal(out[0].puts.length, 50); |
| 83 | + }); |
| 84 | + |
| 85 | + it('splits 51 puts into 50 + 1', () => { |
| 86 | + const out = collect(mkPuts(51)); |
| 87 | + assert.equal(out.length, 2); |
| 88 | + assert.equal(out[0].puts.length, 50); |
| 89 | + assert.equal(out[1].puts.length, 1); |
| 90 | + }); |
| 91 | + |
| 92 | + it('counts puts AND deletes against the same 50-key ceiling (mixed crossing)', () => { |
| 93 | + // 30 puts + 30 deletes = 60 keys → must split (50 then 10), not one batch. |
| 94 | + const deletes = Array.from({ length: 30 }, (_, i) => ({ Key: `d${i}` })); |
| 95 | + const out = collect(mkPuts(30), deletes); |
| 96 | + const totalKeys = out.reduce( |
| 97 | + (n, b) => n + b.puts.length + b.deletes.length, |
| 98 | + 0, |
| 99 | + ); |
| 100 | + assert.equal(totalKeys, 60); // nothing dropped |
| 101 | + assert.ok( |
| 102 | + out.every((b) => b.puts.length + b.deletes.length <= 50), |
| 103 | + 'no batch exceeds the 50-key ceiling', |
| 104 | + ); |
| 105 | + assert.equal(out.length, 2); |
| 106 | + }); |
| 107 | + |
| 108 | + it('flushes on the 3 MB byte ceiling before the key ceiling', () => { |
| 109 | + // Two ~2 MB puts (4 MB total) must land in separate batches even though |
| 110 | + // they are only 2 keys — the byte ceiling trips first. |
| 111 | + const big = 'x'.repeat(2 * 1024 * 1024); |
| 112 | + const out = collect([ |
| 113 | + { Key: 'a', Value: big }, |
| 114 | + { Key: 'b', Value: big }, |
| 115 | + ]); |
| 116 | + assert.equal(out.length, 2); |
| 117 | + }); |
| 118 | + |
| 119 | + it('yields nothing for an empty diff (no-op path)', () => { |
| 120 | + assert.equal(collect([], []).length, 0); |
| 121 | + }); |
| 122 | +}); |
0 commit comments