1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
|
<?php
use PHPUnit\Framework\TestCase;
/**
* Audit tests migrated to the SQLite-backed Database backend to assert against real SQL rows.
*
*/
class AuditTest extends TestCase
{
protected function setUp(): void
{
Database::resetSchema();
// Reset stubs and superglobals
User::reset();
ErrorHandler::reset();
$_POST = [];
$_REQUEST = [];
}
public function testRunInsertsFilteredPostDataAndMetadata(): void
{
// Arrange environment
User::$loggedIn = true;
User::$id = 123;
$_SERVER['REMOTE_ADDR'] = '203.0.113.5';
$_REQUEST['action'] = 'update';
// Build POST with sensitive keys and nested arrays
$_POST = [
'username' => 'alice',
'password' => 'secret', // censored to *****
'apiToken' => 'tok-123', // censored to *****
'my_privatekey' => 'abc', // censored to ***** (ends with key)
'pw_hint' => 'hello', // censored to ***** (starts with pw)
'prevent_autofill' => 'x', // skipped entirely
'action' => 'should-be-skipped-in-data', // skipped in data (but appears in metadata via $_REQUEST)
'arr' => [
'nestedPassword' => 'verysecret', // censored in nested
'value' => 'ok',
],
'long' => str_repeat('A', 1200), // will be truncated to 1000 + ... initially; still under maxTotalLen -> accepted
];
// Act
Audit::run('mod.settings');
// Assert: fetch the last audit row
$row = Database::queryFirst('SELECT * FROM audit ORDER BY id DESC LIMIT 1');
$this->assertNotFalse($row);
$this->assertSame(123, (int)$row['userid']);
$this->assertSame('203.0.113.5', $row['ipaddr']);
$this->assertSame('mod.settings', $row['module']);
$this->assertSame('update', $row['action']);
// Decode filtered JSON
$filtered = json_decode($row['data'], true);
$this->assertIsArray($filtered);
// Skipped keys not present
$this->assertArrayNotHasKey('prevent_autofill', $filtered);
$this->assertArrayNotHasKey('action', $filtered);
// Censored keys
$this->assertSame('*****', $filtered['password']);
$this->assertSame('*****', $filtered['apiToken']);
$this->assertSame('*****', $filtered['my_privatekey']);
$this->assertSame('*****', $filtered['pw_hint']);
// Nested censoring preserved
$this->assertSame('*****', $filtered['arr']['nestedPassword']);
$this->assertSame('ok', $filtered['arr']['value']);
// Truncation behavior for long field: ends with ellipsis and length > 1000 due to ...
$this->assertStringEndsWith('...', $filtered['long']);
$this->assertSame(1003, strlen($filtered['long']));
}
public function testRunExcessPayloadTriggersTraceAndStoresEXCESS(): void
{
// Not logged in -> smaller total limit (10000)
User::$loggedIn = false;
User::$id = 7;
$_SERVER['REMOTE_ADDR'] = '198.51.100.9';
$_REQUEST['action'] = 'bulk';
// Build a very large POST that even after truncation to 200 still exceeds 10000
$big = [];
for ($i = 0; $i < 60; $i++) {
$big['f' . $i] = str_repeat('x', 5000);
}
$_POST = $big;
Audit::run('mod.big');
// Assert row stored with EXCESS data marker
$row = Database::queryFirst('SELECT * FROM audit ORDER BY id DESC LIMIT 1');
$this->assertNotFalse($row);
$this->assertSame('EXCESS', $row['data']);
// ErrorHandler should record the trace
$this->assertNotEmpty(ErrorHandler::$traces);
$this->assertStringContainsString('exceeded', strtolower(ErrorHandler::$traces[0]));
}
}
|