11import assert from "node:assert/strict" ;
2- import { after , test } from "node:test" ;
2+ import { after , afterEach , test } from "node:test" ;
33import { context } from "@opentelemetry/api" ;
44import { isTracingSuppressed } from "@opentelemetry/core" ;
55import { AsyncLocalStorageContextManager } from "@opentelemetry/context-async-hooks" ;
@@ -12,10 +12,12 @@ const originalFetch = globalThis.fetch;
1212const originalKey = process . env . PARLAY_API_KEY ;
1313const manager = new AsyncLocalStorageContextManager ( ) . enable ( ) ;
1414context . setGlobalContextManager ( manager ) ;
15- after ( ( ) => {
15+ afterEach ( ( ) => {
1616 globalThis . fetch = originalFetch ;
1717 if ( originalKey === undefined ) delete process . env . PARLAY_API_KEY ;
1818 else process . env . PARLAY_API_KEY = originalKey ;
19+ } ) ;
20+ after ( ( ) => {
1921 context . disable ( ) ;
2022 manager . disable ( ) ;
2123} ) ;
@@ -91,6 +93,37 @@ test("missing, future and timezone-free market dates remain unknown", () => {
9193 }
9294} ) ;
9395
96+ test ( "calendar-invalid and malformed timestamps cannot become fresh through normalization" , ( ) => {
97+ for ( const value of [
98+ "2026-02-30T00:00:00Z" , "2026-02-29T00:00:00Z" , "1900-02-29T00:00:00Z" ,
99+ "2026-04-31T00:00:00Z" , "2026-01-00T00:00:00Z" , "2026-13-01T00:00:00Z" ,
100+ "2026-03-01T24:00:00Z" , "2026-03-01T00:60:00Z" , "2026-03-01T00:00:60Z" ,
101+ "2026-03-01T00:00:00+24:00" , "2026-03-01T00:00:00+00:60" ,
102+ "March 2, 2026 00:00:00Z" , "2026-03-02 00:00:00Z" ,
103+ ] ) {
104+ const body = sample ( ) ;
105+ body [ 0 ] . bookmakers [ 0 ] . markets [ 0 ] . last_update = value ;
106+ const parsed = Date . parse ( value ) ;
107+ const result = summarizeResponse ( body , options , Number . isFinite ( parsed ) ? parsed + 20_000 : now ) ;
108+ assert . equal ( result . unknownMarketAgeGroups , 1 , value ) ;
109+ assert . equal ( result . completeAndFreshGroups , 0 , value ) ;
110+ }
111+ } ) ;
112+
113+ test ( "valid leap dates, fractions and UTC offsets preserve market age" , ( ) => {
114+ for ( const value of [
115+ "2024-02-29T23:59:59Z" , "2000-02-29T00:00:00Z" ,
116+ "2026-03-02T00:00:00.123Z" , "2026-03-02T05:30:00+05:30" ,
117+ "2026-03-01T19:00:00-05:00" ,
118+ ] ) {
119+ const body = sample ( ) ;
120+ body [ 0 ] . bookmakers [ 0 ] . markets [ 0 ] . last_update = value ;
121+ const result = summarizeResponse ( body , options , Date . parse ( value ) + 20_000 ) ;
122+ assert . equal ( result . oldestKnownMarketAgeSeconds , 20 , value ) ;
123+ assert . equal ( result . completeAndFreshGroups , 1 , value ) ;
124+ }
125+ } ) ;
126+
94127test ( "wrong sport, bookmaker and market fail closed" , ( ) => {
95128 for ( const change of [ "sport" , "book" , "market" ] ) {
96129 const body = sample ( ) ;
@@ -132,6 +165,7 @@ test("live request is single, private and trace-suppressed through body reading"
132165} ) ;
133166
134167test ( "errors, truncation, malformed JSON and oversize bodies are redacted without retry" , async ( ) => {
168+ process . env . PARLAY_API_KEY = "PRIVATE_KEY" ;
135169 for ( const response of [
136170 ( ) => new Response ( "PRIVATE_SECRET" , { status : 401 } ) ,
137171 ( ) => new Response ( "PRIVATE_SECRET" , { status : 429 } ) ,
@@ -152,10 +186,14 @@ test("errors, truncation, malformed JSON and oversize bodies are redacted withou
152186} ) ;
153187
154188test ( "invalid input and absent environment key never make a request" , async ( ) => {
155- globalThis . fetch = async ( ) => { assert . fail ( "Invalid input sent a request" ) ; } ;
189+ process . env . PARLAY_API_KEY = "PRIVATE_KEY" ;
190+ let requests = 0 ;
191+ globalThis . fetch = async ( ) => { requests ++ ; return new Response ( "[]" ) ; } ;
156192 for ( const payload of [ null , { apiKey : "PRIVATE_KEY" } , { mode : "live" , ...options , sport : "../secret" } , { mode : "live" , ...options , bookmaker : "pinnacle,other" } , { mode : "live" , ...options , expectedOutcomes : 4 } ] ) {
157193 await assert . rejects ( checkCoverage ( payload ) ) ;
194+ assert . equal ( requests , 0 ) ;
158195 }
159196 delete process . env . PARLAY_API_KEY ;
160197 await assert . rejects ( checkCoverage ( { mode : "live" , ...options } ) ) ;
198+ assert . equal ( requests , 0 ) ;
161199} ) ;
0 commit comments