[
{"name": "never pinged is new", "args": [null, 5000, 3600, 600], "expect": {"state": "new", "dueAt": null, "downAt": null, "overdueSeconds": 0}},
{"name": "just pinged is up", "args": [1000, 1000, 3600, 600], "expect": {"state": "up", "dueAt": 4600, "downAt": 5200, "overdueSeconds": 0}},
{"name": "the due second itself is still up", "args": [1000, 4600, 3600, 600], "expect": {"state": "up", "dueAt": 4600, "downAt": 5200, "overdueSeconds": 0}},
{"name": "one second past due is late", "args": [1000, 4601, 3600, 600], "expect": {"state": "late", "dueAt": 4600, "downAt": 5200, "overdueSeconds": 1}},
{"name": "the last second of the grace time is still late", "args": [1000, 5200, 3600, 600], "expect": {"state": "late", "dueAt": 4600, "downAt": 5200, "overdueSeconds": 600}},
{"name": "one second after the grace time is down", "args": [1000, 5201, 3600, 600], "expect": {"state": "down", "dueAt": 4600, "downAt": 5200, "overdueSeconds": 601}},
{"name": "a job silent for a day is down and says by how much", "args": [1000, 90000, 3600, 600], "expect": {"state": "down", "dueAt": 4600, "downAt": 5200, "overdueSeconds": 85400}},
{"name": "no grace time goes straight from up to down", "args": [1000, 4601, 3600, 0], "expect": {"state": "down", "dueAt": 4600, "downAt": 4600, "overdueSeconds": 1}},
{"name": "a ping stamped in the future (clock skew) is up, not overdue", "args": [2000, 1000, 3600, 600], "expect": {"state": "up", "dueAt": 5600, "downAt": 6200, "overdueSeconds": 0}},
{"name": "a one-minute cron job with a five-minute grace", "args": [1767225600, 1767225720, 60, 300], "expect": {"state": "late", "dueAt": 1767225660, "downAt": 1767225960, "overdueSeconds": 60}},
{"name": "a zero interval is an error", "args": [1000, 2000, 0, 600], "expectError": "intervalSeconds must be at least 1, received 0"},
{"name": "a negative grace time is an error", "args": [1000, 2000, 3600, -1], "expectError": "graceSeconds must not be negative, received -1"},
{"name": "a zero interval is an error even before the first ping", "args": [null, 2000, 0, 600], "expectError": "intervalSeconds must be at least 1, received 0"},
{"name": "a fractional now is an error", "args": [1000, 1.5, 3600, 600], "expectError": "now must be whole seconds, received 1.5"}
]