Files
usenet-indexer/tests/unit/coordinator/tail.spec.ts
T
Daan MeijerandClaude Fable 5.1 1810abd7d8
Test and deploy / test (push) Successful in 58s
Test and deploy / edge (push) Successful in 2m22s
Test and deploy / deploy (push) Successful in 2m53s
The tail planner grows a group's open range; ranges:merge-tail packs the slivers
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>
2026-10-27 10:46:47 +01:00

131 lines
4.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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')
})
})