A round added a row a group whenever 100k articles or 15 minutes had passed, tuned for a tail leased within minutes; with nothing leasing the tail the rows piled up (127k planned tail ranges of a median 14k articles, 97% adjacent). Now a round grows the group's last range when it is a planned, unspooled, untaken row at the tail's priority, up to RANGE_ARTICLES, and only then adds rows; a range taken meanwhile gets the slice as a row. ranges:merge-tail (--dry-run) packs adjacent planned, uncaptured, unspooled, untaken tail ranges of a group and priority into ranges of RANGE_ARTICLES under the lease lock. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
131 lines
4.2 KiB
TypeScript
131 lines
4.2 KiB
TypeScript
import { test } from '@japa/runner'
|
||
import { tailMerges, tailRanges, wildmatOf } from '#services/planner'
|
||
|
||
const options = { rangeArticles: 1000, minArticles: 100, maxDelayMinutes: 15, priority: -1 }
|
||
const now = new Date('2026-10-09T10:00:00Z')
|
||
const minutesAgo = (m: number) => new Date(now.getTime() - m * 60_000)
|
||
|
||
test.group('coordinator / live tail', () => {
|
||
test('a chunk of new articles, or a long enough wait, makes tail ranges', ({ assert }) => {
|
||
const { rows, extended } = tailRanges(
|
||
[
|
||
// Enough new articles: planned at once, cut at rangeArticles.
|
||
{ id: 1, plannedTo: 5000, plannedAt: minutesAgo(2), high: 7500 },
|
||
// A few new articles, recently planned: waits.
|
||
{ id: 2, plannedTo: 100, plannedAt: minutesAgo(2), high: 150 },
|
||
// A few new articles, waited long enough.
|
||
{ id: 3, plannedTo: 100, plannedAt: minutesAgo(20), high: 103 },
|
||
// Nothing new, never planned, not listed: nothing.
|
||
{ id: 4, plannedTo: 100, plannedAt: minutesAgo(60), high: 100 },
|
||
{ id: 5, plannedTo: null, plannedAt: minutesAgo(60), high: 500 },
|
||
{ id: 6, plannedTo: 100, plannedAt: minutesAgo(60), high: null },
|
||
],
|
||
now,
|
||
options
|
||
)
|
||
assert.deepEqual(
|
||
rows.map((r) => [r.group_id, r.start_id, r.end_id, r.next_id, r.priority]),
|
||
[
|
||
[1, 5001, 6000, 5001, -1],
|
||
[1, 6001, 7000, 6001, -1],
|
||
[1, 7001, 7500, 7001, -1],
|
||
[3, 101, 103, 101, -1],
|
||
]
|
||
)
|
||
assert.deepEqual(extended, [])
|
||
})
|
||
|
||
test('an open last range grows up to rangeArticles before rows are added', ({ assert }) => {
|
||
const { rows, extended } = tailRanges(
|
||
[
|
||
// Room in the open range for everything new.
|
||
{
|
||
id: 1,
|
||
plannedTo: 5300,
|
||
plannedAt: minutesAgo(20),
|
||
high: 5350,
|
||
open: { id: 11, start: 5001 },
|
||
},
|
||
// The open range fills up, the rest is cut as rows.
|
||
{
|
||
id: 2,
|
||
plannedTo: 5300,
|
||
plannedAt: minutesAgo(2),
|
||
high: 7500,
|
||
open: { id: 12, start: 5001 },
|
||
},
|
||
// An open range already full: rows only.
|
||
{
|
||
id: 3,
|
||
plannedTo: 6000,
|
||
plannedAt: minutesAgo(2),
|
||
high: 6500,
|
||
open: { id: 13, start: 5001 },
|
||
},
|
||
// Not open (taken, spooled, another priority): rows.
|
||
{ id: 4, plannedTo: 5300, plannedAt: minutesAgo(20), high: 5350, open: null },
|
||
],
|
||
now,
|
||
options
|
||
)
|
||
assert.deepEqual(
|
||
extended.map((e) => [e.id, e.group_id, e.from, e.end_id]),
|
||
[
|
||
[11, 1, 5301, 5350],
|
||
[12, 2, 5301, 6000],
|
||
]
|
||
)
|
||
assert.deepEqual(
|
||
rows.map((r) => [r.group_id, r.start_id, r.end_id]),
|
||
[
|
||
[2, 6001, 7000],
|
||
[2, 7001, 7500],
|
||
[3, 6001, 6500],
|
||
[4, 5301, 5350],
|
||
]
|
||
)
|
||
})
|
||
|
||
test('adjacent ranges of a group and priority pack into rangeArticles; gaps and other priorities break a run', ({
|
||
assert,
|
||
}) => {
|
||
const r = (id: number, group_id: number, start_id: number, end_id: number, priority = -3) => ({
|
||
id,
|
||
group_id,
|
||
start_id,
|
||
end_id,
|
||
priority,
|
||
})
|
||
const merges = tailMerges(
|
||
[
|
||
// 1–300 + 301–600 + 601–900 fill 1000 with 901–1000; 1001–1100 starts the next run alone.
|
||
r(1, 1, 1, 300),
|
||
r(2, 1, 301, 600),
|
||
r(3, 1, 601, 900),
|
||
r(4, 1, 901, 1000),
|
||
r(5, 1, 1001, 1100),
|
||
// A gap, then two adjacent at another priority.
|
||
r(6, 1, 1200, 1250),
|
||
r(7, 1, 1251, 1300, -1),
|
||
r(8, 1, 1301, 1400, -1),
|
||
// Another group; one that would overflow stays alone.
|
||
r(9, 2, 1, 900),
|
||
r(10, 2, 901, 1100),
|
||
r(11, 2, 1101, 1150),
|
||
],
|
||
1000
|
||
)
|
||
assert.deepEqual(merges, [
|
||
{ id: 1, end_id: 1000, drop: [2, 3, 4] },
|
||
{ id: 7, end_id: 1400, drop: [8] },
|
||
{ id: 10, end_id: 1150, drop: [11] },
|
||
])
|
||
})
|
||
|
||
test('LIST ACTIVE patterns by the first two name parts', ({ assert }) => {
|
||
assert.equal(wildmatOf('alt.binaries.boneless'), 'alt.binaries.*')
|
||
assert.equal(wildmatOf('alt.binaries.multimedia.anime'), 'alt.binaries.*')
|
||
assert.equal(wildmatOf('alt.binaries'), 'alt.binaries')
|
||
})
|
||
})
|