'use strict'; const test = require('node:test'); const assert = require('node:assert/strict'); const { batchFlags, publishedDates, chapterDates, peerDates, fromPeer } = require('../chapter-dates'); const { earliestCompletion } = require('../reading-analytics'); test('bulk imports are flagged, weekly uploads are not', () => { const flags = batchFlags([ '2024-09-07T10:00:00Z', '2024-09-07T10:00:03Z', '2024-09-07T10:04:00Z', // one import '2026-10-11T15:05:58Z', '2026-10-18T15:06:05Z', // weekly releases '2026-10-20T09:00:00Z', '2026-10-20T09:01:00Z', // a double release stays a release undefined, ]); assert.deepEqual(flags, [true, true, true, false, false, false, false, null]); }); test('publishing keeps the first arrival and the earliest ecosystem date', () => { const now = Date.parse('2026-10-24T12:00:00Z'); const fresh = publishedDates({}, { source: { at: '2026-10-18T15:06:05Z', name: 'WeebCentral', batch: false } }, now); assert.equal(fresh.added, '2026-10-24T12:00:00.000Z'); assert.deepEqual(fresh['x-dates'].ecosystem, { at: '2026-10-24T12:00:00.000Z', via: 'this server' }); assert.equal(fresh['x-dates'].source.name, 'WeebCentral'); // Replacing a version later: "added" and the ecosystem date survive. const replaced = publishedDates(fresh, {}, now + 864e5); assert.equal(replaced.added, fresh.added); assert.deepEqual(replaced['x-dates'].ecosystem, fresh['x-dates'].ecosystem); assert.equal(replaced['x-dates'].source.at, '2026-10-18T15:06:05.000Z'); // A peer that had it earlier lowers the date; a later claim never raises it. const earlier = publishedDates(fresh, { ecosystem: { at: '2026-10-19T08:00:00Z', via: 'peer bob' } }, now); assert.deepEqual(earlier['x-dates'].ecosystem, { at: '2026-10-19T08:00:00.000Z', via: 'peer bob' }); const later = publishedDates(earlier, { ecosystem: { at: '2026-10-23T00:00:00Z', via: 'peer eve' } }, now); assert.equal(later['x-dates'].ecosystem.via, 'peer bob'); }); test('peer ecosystem claims cannot predate the source upload or lie in the future', () => { const now = Date.parse('2026-10-24T12:00:00Z'); const source = { at: '2026-10-18T15:06:05Z', name: 'WeebCentral', batch: false }; const early = publishedDates({}, { source, ecosystem: { at: '2001-01-01T00:00:00Z', via: 'peer x' } }, now); assert.equal(early['x-dates'].ecosystem.at, '2026-10-18T15:06:05.000Z'); const future = publishedDates({}, { ecosystem: { at: '2099-01-01T00:00:00Z', via: 'peer x' } }, now); assert.equal(future['x-dates'].ecosystem.at, '2026-10-24T12:00:00.000Z'); }); test('release date tiers: official, source upload, ecosystem, library', () => { const added = Date.parse('2026-10-17T15:04:03Z'); const version = dates => ({ added, meta: { 'x-dates': dates } }); const ecosystem = { at: '2026-10-10T00:00:00Z', via: 'peer bob' }; assert.equal(chapterDates([version({})]).tier, 'library'); assert.equal(chapterDates([version({ ecosystem })]).release.at, '2026-10-10T00:00:00.000Z'); const batch = chapterDates([version({ ecosystem, source: { at: '2024-09-07T10:00:00Z', name: 'WeebCentral', batch: true } })]); assert.equal(batch.tier, 'ecosystem', 'a bulk upload is no release date'); assert.equal(batch.known.batch.at, '2024-09-07T10:00:00.000Z'); const source = chapterDates([version({ ecosystem }), version({ source: { at: '2026-10-04T15:06:05Z', name: 'MangaDex', batch: false } })]); assert.equal(source.tier, 'source'); assert.equal(source.known.ecosystem.at, '2026-10-10T00:00:00.000Z', 'earliest across versions'); const official = chapterDates([version({ release: { at: '2023-09-17', precision: 'day', source: 'MangaUpdates · Viz' }, ecosystem })]); assert.deepEqual(official.release, { at: '2023-09-17', precision: 'day', name: 'MangaUpdates · Viz' }); assert.equal(official.tier, 'official'); }); test('peers exchange cleaned dates and the receiver records the peer', () => { const meta = { added: '2026-10-17T15:04:03Z', secret: 'x', 'x-dates': { source: { at: '2026-10-18T15:06:05Z', name: 'WeebCentral', batch: false, extra: 'drop' } } }; const sent = peerDates(meta, 0); assert.deepEqual(sent, { ecosystem: '2026-10-17T15:04:03.000Z', source: { at: '2026-10-18T15:06:05.000Z', name: 'WeebCentral', batch: false } }); const received = fromPeer(sent, 'bob'); assert.deepEqual(received.ecosystem, { at: '2026-10-17T15:04:03.000Z', via: 'peer bob' }); assert.deepEqual(fromPeer('nonsense', 'bob'), {}); assert.deepEqual(fromPeer({ ecosystem: 'not a date' }, 'bob'), {}); }); test('legacy reads get the earliest completion the reading clock remembers', () => { assert.equal(earliestCompletion(null), null); const record = { completedPasses: 3, currentPass: { completedAt: 5000 }, recentSessions: [{ pass: { completedAt: 3000 } }, { pass: { completedAt: null } }, { pass: { completedAt: 5000 } }] }; assert.deepEqual(earliestCompletion(record), { at: 3000, approx: true }); assert.deepEqual(earliestCompletion({ ...record, completedPasses: 2 }), { at: 3000, approx: false }); });