Lets a technician record when a site should next be looked at, and surfaces that in the dashboard so it can be used for planning. Server: - migration 0002 adds healthchecks.next_due, a VARCHAR(10) 'YYYY-MM-DD' calendar date rather than a timestamp — it's a diary date with no time-of-day, and a timestamp would render as the wrong day off-server. - Kept per-session rather than on a sites table, so the history of what was scheduled when is preserved. A site's current next-due is the value on its most recent session: if the latest visit scheduled nothing, the site reads as unscheduled rather than showing the just-completed visit as overdue. - Validate::optionalDate() round-trips through createFromFormat, which rejects both '2026-2-3' and '2026-02-30' (silently rolled to March 2nd). - next_due follows the same missing-vs-null PUT contract as finished_at, and lives in its own Store::setNextDue() so finishing or reopening a session never disturbs the date and vice versa. - Dashboard shows it on the site list, the per-site session table and the session detail, flagged overdue / today / soon (within a fortnight). Plugin: - Date control on both the active and finished panels — the moment you know when to return is often wrap-up, after the report is generated. - Write-through like every other mutation. Bad input is rejected with a notice rather than silently clearing an existing date. - Included in both the Markdown and HTML reports. Tests (new — tests/, export-ignored from the plugin zip): - Hand-rolled harness, no composer/PHPUnit, in keeping with the repo. - 42 tests: plugin-side date parsing units, plus server integration tests driven over real HTTP against a temp `php -S` instance with a throwaway SQLite DB, so a real server/config.php is never touched. - Mutation-checked: dropping the array_key_exists guard on PUT fails three tests, as intended.
79 lines
3.0 KiB
PHP
79 lines
3.0 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
/**
|
|
* Plugin-side unit tests.
|
|
*
|
|
* These cover the pure static helpers on ATT_HC_Session, which is the only part
|
|
* of the plugin that can be exercised without a WordPress runtime — the class
|
|
* body itself calls no WP functions at load time, so defining ABSPATH is enough
|
|
* to require it.
|
|
*/
|
|
|
|
if (!defined('ABSPATH')) {
|
|
define('ABSPATH', __DIR__ . '/');
|
|
}
|
|
|
|
require_once dirname(__DIR__) . '/includes/class-att-hc-session.php';
|
|
|
|
group('plugin: ATT_HC_Session::sanitise_due_date()');
|
|
|
|
test('accepts a well-formed date', function () {
|
|
assert_same('2026-10-01', ATT_HC_Session::sanitise_due_date('2026-10-01'));
|
|
});
|
|
|
|
test('trims surrounding whitespace', function () {
|
|
assert_same('2026-10-01', ATT_HC_Session::sanitise_due_date(" 2026-10-01\n"));
|
|
});
|
|
|
|
test('accepts a real leap day', function () {
|
|
assert_same('2028-02-29', ATT_HC_Session::sanitise_due_date('2028-02-29'));
|
|
});
|
|
|
|
test('rejects an empty string', function () {
|
|
assert_null(ATT_HC_Session::sanitise_due_date(''));
|
|
assert_null(ATT_HC_Session::sanitise_due_date(' '));
|
|
});
|
|
|
|
test('rejects unpadded components rather than silently normalising them', function () {
|
|
// '2026-2-3' parses, but re-formats to '2026-02-03'. Accepting it would mean
|
|
// the stored value differs from what the user believes they typed.
|
|
assert_null(ATT_HC_Session::sanitise_due_date('2026-2-3'));
|
|
});
|
|
|
|
test('rejects a date that does not exist', function () {
|
|
// createFromFormat would roll this forward to 2026-03-02 without complaint.
|
|
assert_null(ATT_HC_Session::sanitise_due_date('2026-02-30'));
|
|
assert_null(ATT_HC_Session::sanitise_due_date('2027-02-29'), 'not a leap year');
|
|
assert_null(ATT_HC_Session::sanitise_due_date('2026-13-01'), 'month 13');
|
|
assert_null(ATT_HC_Session::sanitise_due_date('2026-00-10'), 'month 0');
|
|
});
|
|
|
|
test('rejects other date formats', function () {
|
|
assert_null(ATT_HC_Session::sanitise_due_date('01/10/2026'));
|
|
assert_null(ATT_HC_Session::sanitise_due_date('1 Oct 2026'));
|
|
assert_null(ATT_HC_Session::sanitise_due_date('2026-10-01T00:00:00Z'));
|
|
assert_null(ATT_HC_Session::sanitise_due_date('20261001'));
|
|
});
|
|
|
|
test('rejects free text', function () {
|
|
assert_null(ATT_HC_Session::sanitise_due_date('next tuesday'));
|
|
assert_null(ATT_HC_Session::sanitise_due_date('soon'));
|
|
assert_null(ATT_HC_Session::sanitise_due_date('<script>alert(1)</script>'));
|
|
});
|
|
|
|
group('plugin: ATT_HC_Session::normalise_site_url()');
|
|
|
|
test('strips scheme, www and trailing slash', function () {
|
|
assert_same('example.com', ATT_HC_Session::normalise_site_url('https://www.example.com/'));
|
|
assert_same('example.com', ATT_HC_Session::normalise_site_url('http://example.com'));
|
|
});
|
|
|
|
test('lowercases the host', function () {
|
|
assert_same('example.com', ATT_HC_Session::normalise_site_url('https://EXAMPLE.com'));
|
|
});
|
|
|
|
test('keeps a subdirectory install path', function () {
|
|
assert_same('example.com/blog', ATT_HC_Session::normalise_site_url('https://example.com/blog/'));
|
|
});
|