fix(ui): проверять период статистики до запроса
This commit is contained in:
1 parent
167e19cd91
commit
94ad73212b
6 files changed
+109
-19
No files matched your search
@@ -0,0 +1,28 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import test from 'node:test'
|
||||
import { isCalendarDate, isStatisticsDate, readStatisticsRange } from './statistics-range'
|
||||
|
||||
const fallback = { from: '2026-09-01', to: '2026-09-07' }
|
||||
const now = Date.parse('2026-09-23T12:00:00Z')
|
||||
const read = (raw: string) => readStatisticsRange(new URLSearchParams(raw), fallback, now)
|
||||
|
||||
test('календарные даты проверяются без нормализации несуществующего дня', () => {
|
||||
for (const date of ['2024-02-29', '2000-02-29', '2026-12-31']) assert.equal(isCalendarDate(date), true)
|
||||
for (const date of ['2026-02-29', '1900-02-29', '2026-04-31', '2026-13-01', '0000-01-01']) assert.equal(isCalendarDate(date), false)
|
||||
assert.equal(isStatisticsDate('2026-02-30T12:00:00Z'), false)
|
||||
assert.equal(isStatisticsDate('2026-09-01T24:00:00Z'), false)
|
||||
assert.equal(isStatisticsDate('2026-09-01T12:00:00'), false)
|
||||
})
|
||||
|
||||
test('пустая ссылка использует пресет, повреждённая не принимается молча', () => {
|
||||
assert.deepEqual(read(''), { range: fallback, error: null })
|
||||
for (const raw of ['from=', 'to=2026-09-01', 'from=bad&to=bad', 'from=2026-09-01&from=2026-09-02&to=2026-09-03', 'from=2026-09-03&to=2026-09-01', 'from=2027-01-01&to=2027-01-02']) assert.ok(read(raw).error, raw)
|
||||
})
|
||||
|
||||
test('границы сравниваются по времени, date-only конец включён как в API', () => {
|
||||
assert.equal(read('from=2026-09-01&to=2026-09-01').error, null)
|
||||
const params = new URLSearchParams({ from: '2026-09-01T12:00:00+07:00', to: '2026-09-01T06:00:00Z' })
|
||||
assert.equal(readStatisticsRange(params, fallback, now).error, null)
|
||||
params.set('to', '2026-09-01T04:00:00Z')
|
||||
assert.ok(readStatisticsRange(params, fallback, now).error)
|
||||
})
|
||||
@@ -0,0 +1,30 @@
|
||||
export function isCalendarDate(value: string): boolean {
|
||||
const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(value)
|
||||
if (!match) return false
|
||||
const year = Number(match[1]), month = Number(match[2]), day = Number(match[3])
|
||||
if (year < 1 || month < 1 || month > 12 || day < 1) return false
|
||||
const leap = year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0)
|
||||
return day <= [31, leap ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month - 1]
|
||||
}
|
||||
|
||||
export function isStatisticsDate(value: string): boolean {
|
||||
if (isCalendarDate(value)) return true
|
||||
const match = /^(\d{4}-\d{2}-\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.\d{1,3})?(Z|[+-]\d{2}:\d{2})$/.exec(value)
|
||||
return Boolean(match && isCalendarDate(match[1]) && Number(match[2]) < 24 && Number(match[3]) < 60 && Number(match[4]) < 60 && Number.isFinite(Date.parse(value)))
|
||||
}
|
||||
|
||||
export function readStatisticsRange(
|
||||
params: URLSearchParams,
|
||||
fallback: { from: string; to: string },
|
||||
now = Date.now(),
|
||||
): { range: { from: string; to: string }; error: string | null } {
|
||||
const from = params.get('from'), to = params.get('to')
|
||||
if (!params.has('from') && !params.has('to')) return { range: fallback, error: null }
|
||||
const invalid = { range: fallback, error: 'Период в ссылке некорректен. Укажите две существующие даты или ISO-время с часовым поясом; начало должно быть раньше конца периода и текущего времени.' }
|
||||
if (params.getAll('from').length !== 1 || params.getAll('to').length !== 1 || !from || !to || !isStatisticsDate(from) || !isStatisticsDate(to)) return invalid
|
||||
const start = Date.parse(from.includes('T') ? from : from + 'T00:00:00Z')
|
||||
// Backend treats date-only `to` as an inclusive UTC day and caps it at now.
|
||||
const end = Date.parse(to.includes('T') ? to : to + 'T00:00:00Z') + (to.includes('T') ? 0 : 86400000)
|
||||
if (start >= Math.min(end, now)) return invalid
|
||||
return { range: { from, to }, error: null }
|
||||
}
|
||||
Reference in new issue
Block a user