30 Commits

Author SHA1 Message Date
renovate[bot]
9ae4901a14 chore(actions): update softprops/action-gh-release action (v2.5.0 → v2.6.1)
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-16 01:41:49 +00:00
renovate[bot]
347faa80d1 chore(deps)!: update marocchino/sticky-pull-request-comment action (v2.9.4 → v3.0.2) (#105) 2026-03-16 03:41:15 +02:00
renovate[bot]
4d9f925e89 chore(actions): update ivuorinen/actions action (v2026.03.06 → v2026.03.11) (#104)
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-13 08:44:56 +02:00
renovate[bot]
d43de372e3 chore(deps): update pre-commit hook igorshubovych/markdownlint-cli (v0.47.0 → v0.48.0) (#103)
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-10 08:04:55 +02:00
renovate[bot]
16a986001c chore(deps): update pre-commit hook bridgecrewio/checkov (3.2.506 → 3.2.508) (#102)
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-09 22:50:51 +02:00
renovate[bot]
8425411b6c chore(deps)!: update dependency phpunit/phpunit (12.5.14 → 13.0.5) (#88)
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-08 19:28:33 +02:00
renovate[bot]
57acc7847f chore(deps)!: update dependency phpunit/phpunit (11.5.55 → 12.5.14) (#87)
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-08 19:21:40 +02:00
d1cbf50c5e fix: switch from xdebug to pcov for code coverage (#101)
* fix: remove xdebug-specific settings from phpunit.xml and composer.json

Remove `<ini name="xdebug.mode" value="coverage"/>` from phpunit.xml and
`XDEBUG_MODE=coverage` prefix from composer scripts. These fail when xdebug
is not installed and are unnecessary when using PCOV for code coverage.

* ci: switch from xdebug to pcov for code coverage

Both test matrix and coverage jobs now use `coverage: pcov`, matching
the existing test-coverage.yaml workflow. PCOV is faster for
coverage-only use and avoids the xdebug dependency conflict.

* build: replace xdebug with pcov in Docker setup

Switch Docker development environment from xdebug to pcov for code
coverage and remove the XDEBUG_MODE environment variable from
docker-compose.yml. Standardizes on pcov across all environments.
2026-03-08 19:16:07 +02:00
renovate[bot]
e26312a6ee chore(deps): update image php to v8.5 (#68)
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-08 14:36:49 +02:00
b0925ce489 refactor: replace hardcoded strings with constant references (#100)
* fix(tests): remove error_log calls and clean up ComprehensiveValidationTest

* refactor: replace hardcoded strings with MaskConstants and TestConstants references

* fix(streaming): replace overcounting '[' heuristic with proper mask detection

StreamingProcessor::getStatistics() was counting any message containing '['
as masked, causing false positives. Now checks for specific mask constants
(MASK_GENERIC, MASK_BRACKETS, MASK_REDACTED_BRACKETS) instead.

Also adds MASK_REDACTED_BRACKETS constant to MaskConstants and removes
the now-unnecessary UnusedFunctionCall psalm suppression.

* refactor(tests): replace remaining hardcoded literals with constant references

Add new constants to TestConstants (MASK_REDACTED_PLAIN, MASK_SECRET_BRACKETS,
MASK_SSN_BRACKETS, PATTERN_REDOS_NESTED_STAR, FIELD_USER_SSN, FIELD_USER_DATA)
and replace all matching literals across 21 test files.

Also removes dead memory_get_usage() call and uses existing
TestConstants::IP_ADDRESS_PUBLIC for hardcoded IP.

* fix(streaming): replace mask-token heuristic with accurate record comparison in getStatistics()

The previous implementation only detected masking when specific mask tokens
appeared in the message, missing cases where context was masked or different
mask values were used. Compare original vs processed records instead.

* refactor(tests): add PATTERN_EMAIL_SIMPLE, MASK_CARD_BRACKETS, EXPECTED_SSN_MASKED constants

Replace cross-file duplicate literals with TestConstants references:
- Email regex (4 files), '[CARD]' (2 files), 'SSN: [SSN]' (2 files)

* fix(streaming): bypass audit logger in getStatistics() by calling orchestrator directly

getStatistics() previously routed through processStream()/processChunk() which
triggered the audit logger for each record. A read-only statistics method should
not produce audit side-effects. Now calls orchestrator.process() directly and
processes records one at a time without materializing the entire iterable.

* refactor(tests): fix test quality issues and add PATTERN_CREDIT_CARD constant

- Replace fail() message that leaked sensitive terms with count-only message
- Replace bare 'EMAIL' string with MaskConstants::MASK_EMAIL for consistency
- Remove error_log() debug output from CriticalBugRegressionTest
- Add TestConstants::PATTERN_CREDIT_CARD and replace inline regex in 3 files
2026-03-08 13:50:17 +02:00
e58397a75d ci: harden workflow permissions and fix shellcheck warnings (#99)
* ci: add least-privilege permissions and quote shell variables in CI workflow

* ci: restrict root permissions and quote shell variables in test-coverage workflow

* ci: quote shell variables and group redirects in release workflow
2026-03-08 03:45:56 +02:00
f6b0f864b4 fix: workflows now use .php-version, other fixes (#98)
* ci: use .php-version file in CI coverage and security jobs

* ci: use .php-version file in release workflow

* ci: use .php-version file in phpcs workflow

* ci: use .php-version file in test-coverage workflow

* ci: remove master branch from pr-lint workflow triggers
2026-03-07 23:30:32 +02:00
renovate[bot]
0fd7cd099f chore(deps): update ivuorinen/actions action (v2026.02.24 → v2026.03.06) (#97)
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-07 20:56:38 +02:00
38946574a4 ci: migrate CodeQL to ivuorinen/actions/codeql-analysis (#96)
* ci: migrate codeql to composable workflow

* fix: correct codeql workflow language, queries, permissions, and action ref

- Use 'javascript' instead of 'javascript-typescript' for CodeQL language
- Add queries: security-and-quality parameter
- Set root-level permissions to {}
- Add job-level permissions (actions, contents, packages, security-events)
- Pin action ref to commit hash with version comment
- Fix mangled cron schedule
2026-03-07 18:44:29 +02:00
renovate[bot]
1be44fff9d chore(deps): lock file maintenance (#95)
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-02 09:09:46 +02:00
renovate[bot]
3be9c07d6c chore(deps)!: update actions/upload-artifact (v6.0.0 → v7.0.0) (#94)
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-02 03:45:27 +02:00
renovate[bot]
8ec91aad35 chore(deps): update ivuorinen/actions action (v2026.01.21 → v2026.02.24) (#93)
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-26 21:04:01 +02:00
renovate[bot]
5eb01578d2 chore(deps): update github/codeql-action action (v4.32.0 → v4.32.4) (#90)
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-25 10:09:09 +00:00
renovate[bot]
110598e921 chore(deps): update pre-commit hook rhysd/actionlint (v1.7.10 → v1.7.11) (#92)
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-25 07:57:48 +00:00
renovate[bot]
9af85cb9b1 chore(deps): update pre-commit hook bridgecrewio/checkov (3.2.499 → 3.2.506) (#91)
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-25 07:58:30 +02:00
renovate[bot]
1a60d2b573 chore(deps): lock file maintenance (#89) 2026-02-23 21:41:03 +02:00
renovate[bot]
97ac6b1eae chore(deps): update actions/cache action (v5.0.2 → v5.0.3) (#84)
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-01 10:25:28 +02:00
47564c5cd6 feat!: upgrade min. php version to 8.4 (#86)
* feat: upgrade min php to 7.4, upgrade packages

* chore: update ci/cd, docs, supporting config to php 8.4

* chore: update rest of the docs, supporting config to php 8.4
2026-02-01 10:20:40 +02:00
renovate[bot]
3d3448dcf0 chore(deps): update phpunit/phpunit (11.5.46 → 11.5.50) [security] (#82)
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-31 11:26:11 +02:00
renovate[bot]
f16eb2a095 chore(deps): update github/codeql-action action (v4.31.9 → v4.32.0) (#81)
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-28 16:44:13 +02:00
renovate[bot]
451726a365 chore(deps): update pre-commit hook bridgecrewio/checkov (3.2.497 → 3.2.499) (#79)
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-27 12:15:38 +00:00
renovate[bot]
966618ec5a chore(deps): update ivuorinen/actions action (v2026.01.13 → v2026.01.21) (#78)
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-27 11:49:02 +00:00
renovate[bot]
c3f5ddcc45 chore(deps): update actions/cache action (v5.0.1 → v5.0.2) (#76)
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-27 11:32:05 +00:00
renovate[bot]
e499663b5d chore(deps): update actions/checkout action (v6.0.1 → v6.0.2) (#77)
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-27 10:32:44 +00:00
renovate[bot]
c89bc1ae72 chore(deps): update pre-commit hook adrienverge/yamllint (v1.37.1 → v1.38.0) (#80)
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-27 08:51:37 +02:00
77 changed files with 1192 additions and 1239 deletions

View File

@@ -9,8 +9,8 @@ field-level configuration, and custom callbacks. It is designed for easy integra
## Coding Conventions
- **Language:** PHP 8.2+
- **PHP Version:** Ensure compatibility with PHP 8.2 and above.
- **Language:** PHP 8.4+
- **PHP Version:** Ensure compatibility with PHP 8.4 and above.
- **PSR Standards:** Follow PSR-12 for code style and autoloading.
- **Testing:** Use PHPUnit for all tests. Place tests in the `tests/` directory. Run `composer test` to execute tests.
- All tests should be written in a way that they can run independently.

View File

@@ -8,20 +8,24 @@ on:
pull_request:
branches: [main, develop]
permissions: {}
jobs:
test:
runs-on: ubuntu-latest
permissions:
contents: read
strategy:
fail-fast: false
matrix:
php-version: ["8.2", "8.3", "8.4"]
php-version: ["8.4", "8.5"]
name: PHP ${{ matrix.php-version }}
steps:
- name: Checkout code
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup PHP
uses: shivammathur/setup-php@44454db4f0199b8b9685a5d763dc37cbf79108e1 # 2.36.0
@@ -29,21 +33,21 @@ jobs:
php-version: ${{ matrix.php-version }}
extensions: mbstring, xml, ctype, iconv, intl, json
tools: composer:v2
coverage: xdebug
coverage: pcov
- name: Get composer cache directory
id: composer-cache
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
run: echo "dir=$(composer config cache-files-dir)" >> "$GITHUB_OUTPUT"
- name: Cache composer dependencies
uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1
uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
restore-keys: ${{ runner.os }}-composer-
- name: Install dependencies
run: composer install --prefer-dist --no-progress --no-suggest
run: composer install --prefer-dist --no-progress
- name: Run PHPUnit tests
run: composer test
@@ -63,24 +67,26 @@ jobs:
coverage:
runs-on: ubuntu-latest
name: Coverage
permissions:
contents: read
steps:
- name: Checkout code
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup PHP
uses: shivammathur/setup-php@44454db4f0199b8b9685a5d763dc37cbf79108e1 # 2.36.0
with:
php-version: "8.2"
php-version-file: '.php-version'
extensions: mbstring, xml, ctype, iconv, intl, json
tools: composer:v2
coverage: xdebug
coverage: pcov
- name: Install dependencies
run: composer install --prefer-dist --no-progress --no-suggest
run: composer install --prefer-dist --no-progress
- name: Run tests with coverage
run: composer test:coverage
run: composer test:ci
- name: Upload coverage reports to Codecov
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
@@ -92,20 +98,22 @@ jobs:
security:
runs-on: ubuntu-latest
name: Security Analysis
permissions:
contents: read
steps:
- name: Checkout code
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup PHP
uses: shivammathur/setup-php@44454db4f0199b8b9685a5d763dc37cbf79108e1 # 2.36.0
with:
php-version: "8.2"
php-version-file: '.php-version'
extensions: mbstring, xml, ctype, iconv, intl, json
tools: composer:v2
- name: Install dependencies
run: composer install --prefer-dist --no-progress --no-suggest
run: composer install --prefer-dist --no-progress
- name: Run security audit
run: composer audit

View File

@@ -8,39 +8,27 @@ on:
pull_request:
branches: ["main"]
schedule:
- cron: "30 1 * * 0" # Run at 1:30 AM UTC every Sunday
- cron: "30 1 * * 0"
merge_group:
permissions:
actions: read
contents: read
permissions: {}
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
packages: read
security-events: write
strategy:
fail-fast: false
matrix:
language: ["actions"] # Add languages used in your actions
language: ["actions"]
steps:
- name: Checkout repository
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- name: Initialize CodeQL
uses: github/codeql-action/init@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v4.31.9
- name: CodeQL Analysis
uses: ivuorinen/actions/codeql-analysis@7f6a23b59316795c4b3cb3b3b28dd53e53655a33 # v2026.03.11
with:
languages: ${{ matrix.language }}
language: ${{ matrix.language }}
queries: security-and-quality
- name: Autobuild
uses: github/codeql-action/autobuild@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v4.31.9
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v4.31.9
with:
category: "/language:${{matrix.language}}"

View File

@@ -15,10 +15,10 @@ jobs:
contents: read
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: shivammathur/setup-php@44454db4f0199b8b9685a5d763dc37cbf79108e1 # 2.36.0
with:
php-version: "8.2"
php-version-file: '.php-version'
- name: Install dependencies
run: composer install --no-interaction --prefer-dist
- name: Run PHP_CodeSniffer (PSR-12)

View File

@@ -4,9 +4,9 @@ name: Lint Code Base
on:
push:
branches: [master, main]
branches: [main]
pull_request:
branches: [master, main]
branches: [main]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
@@ -31,4 +31,4 @@ jobs:
steps:
- name: Run PR Lint
# https://github.com/ivuorinen/actions
uses: ivuorinen/actions/pr-lint@cbfddb24339ba1e0129cc50ab5a0045131b8a2ba # v2026.01.13
uses: ivuorinen/actions/pr-lint@7f6a23b59316795c4b3cb3b3b28dd53e53655a33 # v2026.03.11

View File

@@ -17,19 +17,19 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
- name: Setup PHP
uses: shivammathur/setup-php@44454db4f0199b8b9685a5d763dc37cbf79108e1 # 2.36.0
with:
php-version: "8.2"
php-version-file: '.php-version'
extensions: mbstring, xml, ctype, iconv, intl, json
tools: composer:v2
- name: Install dependencies
run: composer install --prefer-dist --no-progress --no-suggest --no-dev --optimize-autoloader
run: composer install --prefer-dist --no-progress --optimize-autoloader
- name: Run tests
run: composer test
@@ -39,7 +39,7 @@ jobs:
- name: Get tag name
id: tag
run: echo "name=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
run: echo "name=${GITHUB_REF#refs/tags/}" >> "$GITHUB_OUTPUT"
- name: Extract changelog for this version
id: changelog
@@ -49,39 +49,28 @@ jobs:
# Get content between this version and next version header
awk '/^## \[${{ steps.tag.outputs.name }}\]/{flag=1; next} /^## \[/{flag=0} flag' CHANGELOG.md > /tmp/changelog.txt
if [ -s /tmp/changelog.txt ]; then
echo "content<<EOF" >> $GITHUB_OUTPUT
cat /tmp/changelog.txt >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
{
echo "content<<EOF"
cat /tmp/changelog.txt
echo "EOF"
} >> "$GITHUB_OUTPUT"
else
echo "content=Release ${{ steps.tag.outputs.name }}" >> $GITHUB_OUTPUT
echo "content=Release ${{ steps.tag.outputs.name }}" >> "$GITHUB_OUTPUT"
fi
else
echo "content=Release ${{ steps.tag.outputs.name }}" >> $GITHUB_OUTPUT
echo "content=Release ${{ steps.tag.outputs.name }}" >> "$GITHUB_OUTPUT"
fi
- name: Create Release
uses: actions/create-release@0cb9c9b65d5d1901c1f53e5e66eaf4afd303e70e # v1.1.4
id: create_release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ steps.tag.outputs.name }}
release_name: ${{ steps.tag.outputs.name }}
body: ${{ steps.changelog.outputs.content }}
draft: false
prerelease: ${{ contains(steps.tag.outputs.name, '-') }}
- name: Archive source code
run: |
mkdir -p release
composer archive --format=zip --dir=release --file=monolog-gdpr-filter-${{ steps.tag.outputs.name }}
- name: Upload release asset
uses: actions/upload-release-asset@e8f9f06c4b078e705bd2ea027f0926603fc9b4d5 # v1.0.2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Create Release
uses: softprops/action-gh-release@153bb8e04406b158c6c84fc1615b65b24149a1fe # v2.6.1
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./release/monolog-gdpr-filter-${{ steps.tag.outputs.name }}.zip
asset_name: monolog-gdpr-filter-${{ steps.tag.outputs.name }}.zip
asset_content_type: application/zip
name: ${{ steps.tag.outputs.name }}
body: ${{ steps.changelog.outputs.content }}
draft: false
prerelease: ${{ contains(steps.tag.outputs.name, '-') }}
files: ./release/monolog-gdpr-filter-${{ steps.tag.outputs.name }}.zip

View File

@@ -23,4 +23,4 @@ jobs:
issues: write
pull-requests: write
steps:
- uses: ivuorinen/actions/stale@cbfddb24339ba1e0129cc50ab5a0045131b8a2ba # v2026.01.13
- uses: ivuorinen/actions/stale@7f6a23b59316795c4b3cb3b3b28dd53e53655a33 # v2026.03.11

View File

@@ -35,8 +35,8 @@ jobs:
steps:
- name: ⤵️ Checkout Repository
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
token: ${{ secrets.GITHUB_TOKEN }}
- name: ⤵️ Sync Latest Labels Definitions
uses: ivuorinen/actions/sync-labels@cbfddb24339ba1e0129cc50ab5a0045131b8a2ba # v2026.01.13
uses: ivuorinen/actions/sync-labels@7f6a23b59316795c4b3cb3b3b28dd53e53655a33 # v2026.03.11

View File

@@ -8,7 +8,7 @@ on:
push:
branches: [main]
permissions: read-all
permissions: {}
jobs:
test:
@@ -19,13 +19,13 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup PHP
uses: shivammathur/setup-php@44454db4f0199b8b9685a5d763dc37cbf79108e1 # 2.36.0
with:
coverage: pcov
php-version: "8.2"
php-version-file: '.php-version'
- name: Install dependencies
run: composer install --no-interaction --prefer-dist
@@ -38,7 +38,7 @@ jobs:
run: composer test:ci
- name: Upload coverage report
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: coverage-report
path: coverage.xml
@@ -51,11 +51,11 @@ jobs:
- name: "Add Code Coverage to Job Summary"
run: |
cat code-coverage-summary.md >> $GITHUB_STEP_SUMMARY
cat code-coverage-details.md >> $GITHUB_STEP_SUMMARY
cat code-coverage-summary.md >> "$GITHUB_STEP_SUMMARY"
cat code-coverage-details.md >> "$GITHUB_STEP_SUMMARY"
- name: "Add Code Coverage Summary as PR Comment"
uses: marocchino/sticky-pull-request-comment@773744901bac0e8cbb5a0dc842800d45e9b2b405 # v2.9.4
uses: marocchino/sticky-pull-request-comment@70d2764d1a7d5d9560b100cbea0077fc8f633987 # v3.0.2
if: github.event_name == 'pull_request'
with:
recreate: true

1
.php-version Normal file
View File

@@ -0,0 +1 @@
8.4

View File

@@ -22,13 +22,13 @@ repos:
args: [--autofix, --no-sort-keys]
- repo: https://github.com/igorshubovych/markdownlint-cli
rev: v0.47.0
rev: v0.48.0
hooks:
- id: markdownlint
args: [-c, .markdownlint.json, --fix]
- repo: https://github.com/adrienverge/yamllint
rev: v1.37.1
rev: v1.38.0
hooks:
- id: yamllint
@@ -44,18 +44,13 @@ repos:
args: ["--severity=warning"]
- repo: https://github.com/rhysd/actionlint
rev: v1.7.10
rev: v1.7.11
hooks:
- id: actionlint
args: ["-shellcheck="]
- repo: https://github.com/renovatebot/pre-commit-hooks
rev: 42.84.0
hooks:
- id: renovate-config-validator
- repo: https://github.com/bridgecrewio/checkov.git
rev: "3.2.497"
rev: "3.2.508"
hooks:
- id: checkov
args:

View File

@@ -82,7 +82,7 @@ The library can be integrated with Laravel in two ways:
## Code Standards
- **PHP 8.2+** with strict types
- **PHP 8.4+** with strict types
- **PSR-12** coding standard (enforced by PHP_CodeSniffer)
- **Psalm Level 5** static analysis with conservative configuration
- **PHPStan Level 6** for additional code quality insights

View File

@@ -24,7 +24,7 @@ Please be respectful in all interactions.
### Prerequisites
- PHP 8.2 or higher
- PHP 8.4 or higher
- Composer
- Git

View File

@@ -32,7 +32,7 @@ custom callbacks, and advanced features like streaming, rate limiting, and k-ano
## Requirements
- PHP 8.2 or higher
- PHP 8.4 or higher
- Monolog 3.x
## Installation
@@ -404,7 +404,7 @@ $processor = GdprProcessorBuilder::create()
`GdprProcessor::getDefaultPatterns()` includes patterns for:
| Category | Data Types |
|----------|------------|
| -------- | ---------- |
| Personal IDs | Finnish SSN (HETU), US SSN, Passport numbers, National IDs |
| Financial | Credit cards, IBAN, Bank account numbers |
| Contact | Email addresses, Phone numbers (E.164) |

View File

@@ -13,10 +13,10 @@
We actively support the following versions with security updates:
| Version | Supported | PHP Requirements |
| ------- | ------------------ | ---------------- |
| 2.x | Active support | PHP 8.2+ |
| 1.x | ⚠️ Security fixes only | PHP 8.2+ |
| Version | Supported | PHP Requirements |
| ------- | -------------------- | ---------------- |
| 2.x | Active support | PHP 8.4+ |
| 1.x | Security fixes only | PHP 8.4+ |
## Security Features

View File

@@ -9,7 +9,7 @@ This file tracks remaining issues, improvements, and feature requests for the mo
- **141 PHP files** (60 source files, 81 test files)
- **1,346 tests** with **100% success rate** (3,386 assertions)
- **85.07% line coverage**, **88.31% method coverage**
- **PHP 8.2+** with modern language features and strict type safety
- **PHP 8.4+** with modern language features and strict type safety
- **Zero Critical Issues**: All functionality-blocking bugs resolved
- **Static Analysis**: All tools pass cleanly (Psalm, PHPStan, Rector, PHPCS)

View File

@@ -20,9 +20,9 @@
"@lint:tool:md:fix",
"@lint:tool:ec:fix"
],
"test": "XDEBUG_MODE=coverage ./vendor/bin/phpunit --coverage-text",
"test:coverage": "XDEBUG_MODE=coverage ./vendor/bin/phpunit --coverage-text --coverage-html=coverage",
"test:ci": "XDEBUG_MODE=coverage ./vendor/bin/phpunit --teamcity --coverage-clover=coverage.xml",
"test": "./vendor/bin/phpunit --coverage-text",
"test:coverage": "./vendor/bin/phpunit --coverage-text --coverage-html=coverage",
"test:ci": "./vendor/bin/phpunit --teamcity --coverage-clover=coverage.xml",
"lint:tool:ec": "./vendor/bin/ec *.md *.json *.yml *.yaml *.xml *.php",
"lint:tool:ec:fix": "./vendor/bin/ec *.md *.json *.yml *.yaml *.xml *.php --fix",
"lint:tool:phpcs": "./vendor/bin/phpcs src/ tests/ examples/ config/ rector.php --warning-severity=0",
@@ -31,11 +31,11 @@
"lint:tool:psalm": "./vendor/bin/psalm --show-info=true",
"lint:tool:psalm:fix": "./vendor/bin/psalm --alter --issues=MissingReturnType,MissingParamType,MissingClosureReturnType",
"lint:tool:rector": "./vendor/bin/rector",
"lint:tool:md:fix": "markdownlint -f '**/*.md'",
"lint:tool:md": "markdownlint '**/*.md'"
"lint:tool:md:fix": "npx -y markdownlint-cli -f '**/*.md'",
"lint:tool:md": "npx -y markdownlint-cli '**/*.md'"
},
"require": {
"php": "^8.2",
"php": "^8.4",
"monolog/monolog": "^3.0",
"adbario/php-dot-notation": "^3.3"
},
@@ -47,7 +47,7 @@
"illuminate/contracts": "*",
"illuminate/http": "*",
"orklah/psalm-strict-equality": "^3.1",
"phpunit/phpunit": "^11",
"phpunit/phpunit": "^13",
"psalm/plugin-phpunit": "^0.19.5",
"rector/rector": "^2.1",
"squizlabs/php_codesniffer": "^4.0",

1383
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,4 @@
FROM php:8.2-cli-alpine
FROM php:8.5-cli-alpine
# Install system dependencies
RUN apk add --no-cache \
@@ -15,12 +15,10 @@ RUN apk add --no-cache \
# Install Composer
COPY --from=composer:2 /usr/bin/composer /usr/bin/composer
# Install and configure Xdebug for code coverage
# Install PCOV for code coverage
RUN apk add --no-cache $PHPIZE_DEPS \
&& pecl install xdebug \
&& docker-php-ext-enable xdebug \
&& echo "xdebug.mode=coverage,debug" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini \
&& echo "xdebug.client_host=host.docker.internal" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini
&& pecl install pcov \
&& docker-php-ext-enable pcov
# Set working directory
WORKDIR /app

View File

@@ -11,14 +11,13 @@ services:
working_dir: /app
environment:
- COMPOSER_HOME=/home/developer/.composer
- XDEBUG_MODE=coverage
stdin_open: true
tty: true
command: tail -f /dev/null
# PHP 8.3 for testing compatibility
php83:
image: php:8.3-cli-alpine
image: php:8.5-cli-alpine
volumes:
- ..:/app
working_dir: /app

View File

@@ -26,7 +26,7 @@ docker compose exec php composer lint
### docker/Dockerfile
```dockerfile
FROM php:8.2-cli-alpine
FROM php:8.4-cli-alpine
# Install system dependencies
RUN apk add --no-cache \
@@ -90,9 +90,9 @@ services:
tty: true
command: tail -f /dev/null
# Optional: PHP 8.3 for testing compatibility
# Optional: PHP 8.5 for testing compatibility
php83:
image: php:8.3-cli-alpine
image: php:8.5-cli-alpine
volumes:
- ..:/app
working_dir: /app
@@ -253,7 +253,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
php: ['8.2', '8.3']
php: ['8.4', '8.5']
steps:
- uses: actions/checkout@v4

View File

@@ -28,7 +28,7 @@ Plugins extend the GDPR processor's functionality without modifying core code. U
### When to Use Plugins vs. Configuration
| Scenario | Use Plugin | Use Configuration |
|----------|-----------|-------------------|
| -------- | --------- | ----------------- |
| Add regex patterns | ✅ (via `getPatterns()`) | ✅ (via constructor) |
| Custom transformation logic | ✅ | ❌ |
| Conditional processing | ✅ | ❌ |
@@ -126,7 +126,7 @@ interface MaskingPluginInterface
### Method Reference
| Method | Purpose | When Called |
|--------|---------|-------------|
| ------ | ------- | ----------- |
| `getName()` | Unique identifier for debugging | On registration |
| `preProcessContext()` | Modify context before masking | Before core masking |
| `preProcessMessage()` | Modify message before masking | Before core masking |
@@ -266,7 +266,7 @@ class LowPriorityPlugin extends AbstractMaskingPlugin
### Recommended Priority Ranges
| Range | Use Case | Example |
|-------|----------|---------|
| ----- | -------- | ------- |
| 1-50 | Security/validation | Input sanitization |
| 50-100 | Standard processing | Pattern masking |
| 100-150 | Business logic | Domain-specific rules |

View File

@@ -22,7 +22,7 @@ This guide helps diagnose and resolve common issues with the Monolog GDPR Filter
```bash
# Check PHP version
php -v # Must be 8.2 or higher
php -v # Must be 8.4 or higher
# Clear Composer cache
composer clear-cache

View File

@@ -91,7 +91,7 @@ parameters:
- '#Constant Tests\\.*::.* is unused#'
# PHP version for analysis
phpVersion: 80200
phpVersion: 80400
# Stub files for missing functions/classes
stubFiles: []

View File

@@ -21,8 +21,4 @@
</source>
<coverage/>
<php>
<ini name="xdebug.mode" value="coverage"/>
</php>
</phpunit>

View File

@@ -4,7 +4,7 @@
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="https://getpsalm.org/schema/config"
xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
phpVersion="8.2"
phpVersion="8.4"
noCache="false"
findUnusedPsalmSuppress="true"
skipChecksOnUnresolvableIncludes="true"
@@ -115,13 +115,6 @@
</errorLevel>
</UndefinedFunction>
<!-- Test utility calls -->
<UnusedFunctionCall>
<errorLevel type="suppress">
<directory name="tests" />
</errorLevel>
</UnusedFunctionCall>
<!-- Laravel-specific patterns -->
<!-- (Laravel directory is excluded from scanning) -->

View File

@@ -57,7 +57,7 @@ return RectorConfig::configure()
removeUnusedImports: true, // This is generally safe
)
// Conservative PHP version targeting
->withPhpVersion(80200)
->withPhpVersion(80400)
// Don't use prepared sets - they're too aggressive
->withPreparedSets(
deadCode: false, // Disable dead code removal

View File

@@ -28,6 +28,7 @@ final class MaskConstants
public const MASK_REDACTED = '***REDACTED***';
public const MASK_FILTERED = '***FILTERED***';
public const MASK_BRACKETS = '[MASKED]';
public const MASK_REDACTED_BRACKETS = '[REDACTED]';
// Personal identifiers
public const MASK_HETU = '***HETU***'; // Finnish SSN

View File

@@ -176,10 +176,10 @@ final class StreamingProcessor
{
$stats = ['processed' => 0, 'masked' => 0, 'errors' => 0];
foreach ($this->processStream($records) as $record) {
foreach ($records as $record) {
$stats['processed']++;
// Count if any masking occurred (simple heuristic)
if (str_contains($record['message'], '***') || str_contains($record['message'], '[')) {
$processed = $this->orchestrator->process($record['message'], $record['context']);
if ($processed !== $record) {
$stats['masked']++;
}
}

View File

@@ -35,7 +35,7 @@ class AdvancedRegexMaskProcessorTest extends TestCase
];
$fieldPaths = [
"user.ssn" => "[GDPR]",
TestConstants::FIELD_USER_SSN => "[GDPR]",
"payment.card" => "[CC]",
"contact.email" => FieldMaskConfig::useProcessorPatterns(), // use regex-masked
"metadata.session" => "[SESSION]",
@@ -48,7 +48,7 @@ class AdvancedRegexMaskProcessorTest extends TestCase
{
$record = $this->logEntry()->with(message: "Card: 1234567812345678");
$result = ($this->processor)($record)->toArray();
$this->assertSame("Card: " . MaskConstants::MASK_CC, $result["message"]);
$this->assertSame("Card: " . MaskConstants::MASK_CC, $result[TestConstants::FIELD_MESSAGE]);
}
public function testMaskEmailInMessage(): void
@@ -56,7 +56,7 @@ class AdvancedRegexMaskProcessorTest extends TestCase
$record = $this->logEntry()->with(message: "Email: user@example.com");
$result = ($this->processor)($record)->toArray();
$this->assertSame("Email: " . MaskConstants::MASK_EMAIL, $result["message"]);
$this->assertSame("Email: " . MaskConstants::MASK_EMAIL, $result[TestConstants::FIELD_MESSAGE]);
}
public function testContextFieldPathReplacements(): void

View File

@@ -19,11 +19,11 @@ final class KAnonymizerTest extends TestCase
$anonymizer = new KAnonymizer();
$anonymizer->registerAgeStrategy('age');
$record = ['name' => 'John', 'age' => 25];
$record = ['name' => TestConstants::NAME_FIRST, 'age' => 25];
$result = $anonymizer->anonymize($record);
$this->assertSame(TestConstants::AGE_RANGE_20_29, $result['age']);
$this->assertSame('John', $result['name']);
$this->assertSame(TestConstants::NAME_FIRST, $result['name']);
}
public function testAnonymizeWithAgeStrategyDifferentRanges(): void
@@ -84,7 +84,7 @@ final class KAnonymizerTest extends TestCase
$anonymizer = new KAnonymizer();
$anonymizer->registerLocationStrategy('zip_code', 3);
$record = ['zip_code' => '12345'];
$record = ['zip_code' => TestConstants::DATA_NUMBER_STRING];
$result = $anonymizer->anonymize($record);
$this->assertSame('123**', $result['zip_code']);
@@ -115,17 +115,17 @@ final class KAnonymizerTest extends TestCase
public function testAnonymizeWithCustomStrategy(): void
{
$anonymizer = new KAnonymizer();
$anonymizer->registerCustomStrategy('email', fn(mixed $v): string => explode('@', (string) $v)[1] ?? 'unknown');
$anonymizer->registerCustomStrategy(TestConstants::CONTEXT_EMAIL, fn(mixed $v): string => explode('@', (string) $v)[1] ?? 'unknown');
$record = ['email' => 'john@example.com'];
$record = [TestConstants::CONTEXT_EMAIL => TestConstants::EMAIL_JOHN];
$result = $anonymizer->anonymize($record);
$this->assertSame('example.com', $result['email']);
$this->assertSame(TestConstants::DOMAIN, $result[TestConstants::CONTEXT_EMAIL]);
}
public function testRegisterStrategy(): void
{
$strategy = new GeneralizationStrategy(fn(mixed $v): string => 'masked', 'test');
$strategy = new GeneralizationStrategy(fn(mixed $v): string => TestConstants::DATA_MASKED, 'test');
$anonymizer = new KAnonymizer();
$anonymizer->registerStrategy('field', $strategy);
@@ -133,7 +133,7 @@ final class KAnonymizerTest extends TestCase
$record = ['field' => 'value'];
$result = $anonymizer->anonymize($record);
$this->assertSame('masked', $result['field']);
$this->assertSame(TestConstants::DATA_MASKED, $result['field']);
}
public function testAnonymizeIgnoresMissingFields(): void
@@ -141,10 +141,10 @@ final class KAnonymizerTest extends TestCase
$anonymizer = new KAnonymizer();
$anonymizer->registerAgeStrategy('age');
$record = ['name' => 'John'];
$record = ['name' => TestConstants::NAME_FIRST];
$result = $anonymizer->anonymize($record);
$this->assertSame(['name' => 'John'], $result);
$this->assertSame(['name' => TestConstants::NAME_FIRST], $result);
}
public function testAnonymizeBatch(): void
@@ -153,7 +153,7 @@ final class KAnonymizerTest extends TestCase
$anonymizer->registerAgeStrategy('age');
$records = [
['name' => 'John', 'age' => 25],
['name' => TestConstants::NAME_FIRST, 'age' => 25],
['name' => 'Jane', 'age' => 32],
];
@@ -258,13 +258,13 @@ final class KAnonymizerTest extends TestCase
$anonymizer->registerLocationStrategy('zip', 2);
$anonymizer->registerDateStrategy('date', 'year');
$record = ['age' => 28, 'zip' => '12345', 'date' => '2024-06-15', 'name' => 'John'];
$record = ['age' => 28, 'zip' => TestConstants::DATA_NUMBER_STRING, 'date' => '2024-06-15', 'name' => TestConstants::NAME_FIRST];
$result = $anonymizer->anonymize($record);
$this->assertSame(TestConstants::AGE_RANGE_20_29, $result['age']);
$this->assertSame('12***', $result['zip']);
$this->assertSame('2024', $result['date']);
$this->assertSame('John', $result['name']);
$this->assertSame(TestConstants::NAME_FIRST, $result['name']);
}
public function testFluentInterface(): void

View File

@@ -8,6 +8,7 @@ use Ivuorinen\MonologGdprFilter\ArrayAccessor\ArrayAccessorFactory;
use Ivuorinen\MonologGdprFilter\ArrayAccessor\DotArrayAccessor;
use Ivuorinen\MonologGdprFilter\Contracts\ArrayAccessorInterface;
use PHPUnit\Framework\TestCase;
use Tests\TestConstants;
/**
* Tests for ArrayAccessorFactory.
@@ -29,11 +30,11 @@ final class ArrayAccessorFactoryTest extends TestCase
{
$factory = ArrayAccessorFactory::default();
$accessor = $factory->create([
'user' => ['email' => 'test@example.com'],
'user' => [TestConstants::CONTEXT_EMAIL => TestConstants::EMAIL_TEST],
]);
$this->assertTrue($accessor->has('user.email'));
$this->assertSame('test@example.com', $accessor->get('user.email'));
$this->assertTrue($accessor->has(TestConstants::FIELD_USER_EMAIL));
$this->assertSame(TestConstants::EMAIL_TEST, $accessor->get(TestConstants::FIELD_USER_EMAIL));
}
public function testWithClassFactoryMethod(): void

View File

@@ -28,22 +28,22 @@ final class ArrayAccessorInterfaceTest extends TestCase
{
$accessor = new DotArrayAccessor([
'user' => [
'email' => TestConstants::EMAIL_TEST,
TestConstants::CONTEXT_EMAIL => TestConstants::EMAIL_TEST,
],
]);
$this->assertTrue($accessor->has('user.email'));
$this->assertTrue($accessor->has(TestConstants::FIELD_USER_EMAIL));
}
public function testHasReturnsFalseForMissingPath(): void
{
$accessor = new DotArrayAccessor([
'user' => [
'email' => TestConstants::EMAIL_TEST,
TestConstants::CONTEXT_EMAIL => TestConstants::EMAIL_TEST,
],
]);
$this->assertFalse($accessor->has('user.name'));
$this->assertFalse($accessor->has(TestConstants::FIELD_USER_NAME));
$this->assertFalse($accessor->has('nonexistent'));
}
@@ -51,12 +51,12 @@ final class ArrayAccessorInterfaceTest extends TestCase
{
$accessor = new DotArrayAccessor([
'user' => [
'email' => TestConstants::EMAIL_TEST,
TestConstants::CONTEXT_EMAIL => TestConstants::EMAIL_TEST,
'age' => 25,
],
]);
$this->assertSame(TestConstants::EMAIL_TEST, $accessor->get('user.email'));
$this->assertSame(TestConstants::EMAIL_TEST, $accessor->get(TestConstants::FIELD_USER_EMAIL));
$this->assertSame(25, $accessor->get('user.age'));
}
@@ -72,43 +72,43 @@ final class ArrayAccessorInterfaceTest extends TestCase
{
$accessor = new DotArrayAccessor([]);
$accessor->set('user.email', TestConstants::EMAIL_NEW);
$accessor->set(TestConstants::FIELD_USER_EMAIL, TestConstants::EMAIL_NEW);
$this->assertTrue($accessor->has('user.email'));
$this->assertSame(TestConstants::EMAIL_NEW, $accessor->get('user.email'));
$this->assertTrue($accessor->has(TestConstants::FIELD_USER_EMAIL));
$this->assertSame(TestConstants::EMAIL_NEW, $accessor->get(TestConstants::FIELD_USER_EMAIL));
}
public function testSetOverwritesExistingPath(): void
{
$accessor = new DotArrayAccessor([
'user' => ['email' => 'old@example.com'],
'user' => [TestConstants::CONTEXT_EMAIL => 'old@example.com'],
]);
$accessor->set('user.email', TestConstants::EMAIL_NEW);
$accessor->set(TestConstants::FIELD_USER_EMAIL, TestConstants::EMAIL_NEW);
$this->assertSame(TestConstants::EMAIL_NEW, $accessor->get('user.email'));
$this->assertSame(TestConstants::EMAIL_NEW, $accessor->get(TestConstants::FIELD_USER_EMAIL));
}
public function testDeleteRemovesPath(): void
{
$accessor = new DotArrayAccessor([
'user' => [
'email' => TestConstants::EMAIL_TEST,
TestConstants::CONTEXT_EMAIL => TestConstants::EMAIL_TEST,
'name' => 'Test User',
],
]);
$accessor->delete('user.email');
$accessor->delete(TestConstants::FIELD_USER_EMAIL);
$this->assertFalse($accessor->has('user.email'));
$this->assertTrue($accessor->has('user.name'));
$this->assertFalse($accessor->has(TestConstants::FIELD_USER_EMAIL));
$this->assertTrue($accessor->has(TestConstants::FIELD_USER_NAME));
}
public function testAllReturnsCompleteArray(): void
{
$data = [
'user' => [
'email' => TestConstants::EMAIL_TEST,
TestConstants::CONTEXT_EMAIL => TestConstants::EMAIL_TEST,
'profile' => [
'bio' => 'Hello world',
],

View File

@@ -111,10 +111,10 @@ final class ErrorContextTest extends TestCase
public function testSanitizesConnectionStrings(): void
{
$message = 'Failed: redis://admin:password@localhost:6379';
$message = 'Failed: redis://admin:' . TestConstants::CONTEXT_PASSWORD . '@localhost:6379';
$context = ErrorContext::create('ConnError', $message);
$this->assertStringNotContainsString('password', $context->message);
$this->assertStringNotContainsString(TestConstants::CONTEXT_PASSWORD, $context->message);
$this->assertStringContainsString(TestConstants::MASK_REDACTED_BRACKETS, $context->message);
}
@@ -141,7 +141,7 @@ final class ErrorContextTest extends TestCase
{
$context = new ErrorContext(
errorType: 'TestError',
message: 'Test message',
message: TestConstants::MESSAGE_DEFAULT,
code: 100,
file: '/test/file.php',
line: 50,
@@ -151,14 +151,14 @@ final class ErrorContextTest extends TestCase
$array = $context->toArray();
$this->assertArrayHasKey('error_type', $array);
$this->assertArrayHasKey('message', $array);
$this->assertArrayHasKey(TestConstants::FIELD_MESSAGE, $array);
$this->assertArrayHasKey('code', $array);
$this->assertArrayHasKey('file', $array);
$this->assertArrayHasKey('line', $array);
$this->assertArrayHasKey('metadata', $array);
$this->assertSame('TestError', $array['error_type']);
$this->assertSame('Test message', $array['message']);
$this->assertSame(TestConstants::MESSAGE_DEFAULT, $array[TestConstants::FIELD_MESSAGE]);
$this->assertSame(100, $array['code']);
}

View File

@@ -43,7 +43,7 @@ final class StructuredAuditLoggerTest extends TestCase
$this->logs[] = [
'path' => $path,
'original' => $original,
'masked' => $masked
TestConstants::DATA_MASKED => $masked
];
};
}
@@ -52,12 +52,12 @@ final class StructuredAuditLoggerTest extends TestCase
{
$logger = new StructuredAuditLogger($this->createBaseLogger());
$logger->log('user.email', TestConstants::EMAIL_JOHN, TestConstants::MASK_MASKED_BRACKETS);
$logger->log(TestConstants::FIELD_USER_EMAIL, TestConstants::EMAIL_JOHN, TestConstants::MASK_MASKED_BRACKETS);
$this->assertCount(1, $this->logs);
$this->assertSame('user.email', $this->logs[0]['path']);
$this->assertSame(TestConstants::FIELD_USER_EMAIL, $this->logs[0]['path']);
$this->assertSame(TestConstants::EMAIL_JOHN, $this->logs[0]['original']);
$this->assertSame(TestConstants::MASK_MASKED_BRACKETS, $this->logs[0]['masked']);
$this->assertSame(TestConstants::MASK_MASKED_BRACKETS, $this->logs[0][TestConstants::DATA_MASKED]);
}
public function testLogWithContext(): void
@@ -65,7 +65,7 @@ final class StructuredAuditLoggerTest extends TestCase
$logger = new StructuredAuditLogger($this->createBaseLogger());
$context = AuditContext::success(AuditContext::OP_REGEX, 5.0);
$logger->log('user.email', TestConstants::EMAIL_JOHN, TestConstants::MASK_MASKED_BRACKETS, $context);
$logger->log(TestConstants::FIELD_USER_EMAIL, TestConstants::EMAIL_JOHN, TestConstants::MASK_MASKED_BRACKETS, $context);
$this->assertCount(1, $this->logs);
}
@@ -75,15 +75,15 @@ final class StructuredAuditLoggerTest extends TestCase
$logger = new StructuredAuditLogger($this->createBaseLogger());
$logger->logSuccess(
'user.ssn',
'123-45-6789',
'[SSN]',
TestConstants::FIELD_USER_SSN,
TestConstants::SSN_US,
TestConstants::MASK_SSN_BRACKETS,
AuditContext::OP_REGEX,
10.5
);
$this->assertCount(1, $this->logs);
$this->assertSame('user.ssn', $this->logs[0]['path']);
$this->assertSame(TestConstants::FIELD_USER_SSN, $this->logs[0]['path']);
}
public function testLogFailure(): void
@@ -92,14 +92,14 @@ final class StructuredAuditLoggerTest extends TestCase
$error = ErrorContext::create('RegexError', 'Pattern failed');
$logger->logFailure(
'user.data',
TestConstants::FIELD_USER_DATA,
'sensitive value',
AuditContext::OP_REGEX,
$error
);
$this->assertCount(1, $this->logs);
$this->assertSame('[MASKING_FAILED]', $this->logs[0]['masked']);
$this->assertSame('[MASKING_FAILED]', $this->logs[0][TestConstants::DATA_MASKED]);
}
public function testLogRecovery(): void
@@ -107,7 +107,7 @@ final class StructuredAuditLoggerTest extends TestCase
$logger = new StructuredAuditLogger($this->createBaseLogger());
$logger->logRecovery(
'user.email',
TestConstants::FIELD_USER_EMAIL,
TestConstants::EMAIL_JOHN,
TestConstants::MASK_MASKED_BRACKETS,
AuditContext::OP_REGEX,
@@ -131,14 +131,14 @@ final class StructuredAuditLoggerTest extends TestCase
$this->assertCount(1, $this->logs);
$this->assertSame(TestConstants::NAME_FULL, $this->logs[0]['original']);
$this->assertSame(TestConstants::NAME_FULL, $this->logs[0]['masked']);
$this->assertSame(TestConstants::NAME_FULL, $this->logs[0][TestConstants::DATA_MASKED]);
}
public function testWrapStaticFactory(): void
{
$logger = StructuredAuditLogger::wrap($this->createBaseLogger());
$logger->log('test.path', 'original', 'masked');
$logger->log('test.path', 'original', TestConstants::DATA_MASKED);
$this->assertCount(1, $this->logs);
}
@@ -152,7 +152,7 @@ final class StructuredAuditLoggerTest extends TestCase
);
$logger = new StructuredAuditLogger($rateLimited);
$logger->log('user.email', TestConstants::EMAIL_JOHN, TestConstants::MASK_MASKED_BRACKETS);
$logger->log(TestConstants::FIELD_USER_EMAIL, TestConstants::EMAIL_JOHN, TestConstants::MASK_MASKED_BRACKETS);
$this->assertCount(1, $this->logs);
}
@@ -177,7 +177,7 @@ final class StructuredAuditLoggerTest extends TestCase
$wrapped = $logger->getWrappedLogger();
// Verify the wrapped logger works by calling it
$wrapped('test.path', 'original', 'masked');
$wrapped('test.path', 'original', TestConstants::DATA_MASKED);
$this->assertCount(1, $this->logs);
}
@@ -188,7 +188,7 @@ final class StructuredAuditLoggerTest extends TestCase
includeTimestamp: false
);
$logger->log('test', 'original', 'masked');
$logger->log('test', 'original', TestConstants::DATA_MASKED);
$this->assertCount(1, $this->logs);
}
@@ -200,7 +200,7 @@ final class StructuredAuditLoggerTest extends TestCase
includeDuration: false
);
$logger->log('test', 'original', 'masked');
$logger->log('test', 'original', TestConstants::DATA_MASKED);
$this->assertCount(1, $this->logs);
}

View File

@@ -292,7 +292,7 @@ final class GdprProcessorBuilderEdgeCasesTest extends TestCase
#[\Override]
public function getFieldPaths(): array
{
return ['secret.key' => FieldMaskConfig::replace('[REDACTED]')];
return ['secret.key' => FieldMaskConfig::replace(TestConstants::MASK_REDACTED_BRACKETS)];
}
};
@@ -303,7 +303,7 @@ final class GdprProcessorBuilderEdgeCasesTest extends TestCase
$record = $this->createLogRecord('Test', ['secret' => ['key' => 'sensitive-value']]);
$processed = $processor($record);
$this->assertSame('[REDACTED]', $processed->context['secret']['key']);
$this->assertSame(TestConstants::MASK_REDACTED_BRACKETS, $processed->context['secret']['key']);
}
#[Test]
@@ -313,16 +313,16 @@ final class GdprProcessorBuilderEdgeCasesTest extends TestCase
$processor = GdprProcessorBuilder::create()
->withArrayAccessorFactory($factory)
->addFieldPath('user.email', MaskConstants::MASK_EMAIL)
->addFieldPath(TestConstants::FIELD_USER_EMAIL, MaskConstants::MASK_EMAIL)
->build();
$record = $this->createLogRecord('Test', [
'user' => ['email' => 'test@example.com'],
'user' => [TestConstants::CONTEXT_EMAIL => TestConstants::EMAIL_TEST],
]);
$processed = $processor($record);
$this->assertSame(MaskConstants::MASK_EMAIL, $processed->context['user']['email']);
$this->assertSame(MaskConstants::MASK_EMAIL, $processed->context['user'][TestConstants::CONTEXT_EMAIL]);
}
#[Test]
@@ -330,13 +330,13 @@ final class GdprProcessorBuilderEdgeCasesTest extends TestCase
{
$processor = GdprProcessorBuilder::create()
->withMaxDepth(2)
->addPattern('/secret/', TestConstants::MASK_MASKED_BRACKETS)
->addPattern(TestConstants::PATTERN_SECRET, TestConstants::MASK_MASKED_BRACKETS)
->build();
$record = $this->createLogRecord('Test', [
'level1' => [
'level2' => [
'level3' => 'secret data',
'level3' => TestConstants::MESSAGE_SECRET_DATA,
],
],
]);
@@ -353,13 +353,13 @@ final class GdprProcessorBuilderEdgeCasesTest extends TestCase
$auditLogs = [];
$processor = GdprProcessorBuilder::create()
->addFieldPath('password', MaskConstants::MASK_REDACTED)
->addFieldPath(TestConstants::CONTEXT_PASSWORD, MaskConstants::MASK_REDACTED)
->withAuditLogger(function ($path, $original, $masked) use (&$auditLogs): void {
$auditLogs[] = ['path' => $path, 'original' => $original, 'masked' => $masked];
$auditLogs[] = ['path' => $path, 'original' => $original, TestConstants::DATA_MASKED => $masked];
})
->build();
$record = $this->createLogRecord('Test', ['password' => 'secret123']);
$record = $this->createLogRecord('Test', [TestConstants::CONTEXT_PASSWORD => 'secret123']);
$processor($record);
$this->assertNotEmpty($auditLogs);
@@ -390,21 +390,21 @@ final class GdprProcessorBuilderEdgeCasesTest extends TestCase
{
$processor = GdprProcessorBuilder::create()
->addFieldPaths([
'user.email' => MaskConstants::MASK_EMAIL,
TestConstants::FIELD_USER_EMAIL => MaskConstants::MASK_EMAIL,
'user.phone' => MaskConstants::MASK_PHONE,
])
->build();
$record = $this->createLogRecord('Test', [
'user' => [
'email' => 'test@example.com',
TestConstants::CONTEXT_EMAIL => TestConstants::EMAIL_TEST,
'phone' => '555-1234',
],
]);
$processed = $processor($record);
$this->assertSame(MaskConstants::MASK_EMAIL, $processed->context['user']['email']);
$this->assertSame(MaskConstants::MASK_EMAIL, $processed->context['user'][TestConstants::CONTEXT_EMAIL]);
$this->assertSame(MaskConstants::MASK_PHONE, $processed->context['user']['phone']);
}
@@ -413,15 +413,15 @@ final class GdprProcessorBuilderEdgeCasesTest extends TestCase
{
$processor = GdprProcessorBuilder::create()
->addPatterns([
'/\d{3}-\d{2}-\d{4}/' => '[SSN]',
'/\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/' => '[EMAIL]',
TestConstants::PATTERN_SSN_FORMAT => TestConstants::MASK_SSN_BRACKETS,
TestConstants::PATTERN_EMAIL_FULL => TestConstants::MASK_EMAIL_BRACKETS,
])
->build();
$record = $this->createLogRecord('SSN: 123-45-6789, Email: user@example.com');
$record = $this->createLogRecord('SSN: ' . TestConstants::SSN_US . ', Email: ' . TestConstants::EMAIL_USER);
$processed = $processor($record);
$this->assertStringContainsString('[SSN]', $processed->message);
$this->assertStringContainsString('[EMAIL]', $processed->message);
$this->assertStringContainsString(TestConstants::MASK_SSN_BRACKETS, $processed->message);
$this->assertStringContainsString(TestConstants::MASK_EMAIL_BRACKETS, $processed->message);
}
}

View File

@@ -58,7 +58,7 @@ final class GdprProcessorBuilderTest extends TestCase
{
$patterns = [
TestConstants::PATTERN_DIGITS => TestConstants::MASK_DIGITS_BRACKETS,
TestConstants::PATTERN_TEST => '[TEST]',
TestConstants::PATTERN_TEST => TestConstants::REPLACEMENT_TEST,
];
$builder = GdprProcessorBuilder::create()->addPatterns($patterns);
@@ -70,7 +70,7 @@ final class GdprProcessorBuilderTest extends TestCase
{
$builder = GdprProcessorBuilder::create()
->addPattern(TestConstants::PATTERN_DIGITS, TestConstants::MASK_DIGITS_BRACKETS)
->setPatterns([TestConstants::PATTERN_TEST => '[TEST]']);
->setPatterns([TestConstants::PATTERN_TEST => TestConstants::REPLACEMENT_TEST]);
$patterns = $builder->getPatterns();
@@ -82,7 +82,7 @@ final class GdprProcessorBuilderTest extends TestCase
public function testAddFieldPath(): void
{
$builder = GdprProcessorBuilder::create()
->addFieldPath(TestConstants::CONTEXT_EMAIL, FieldMaskConfig::replace('[EMAIL]'));
->addFieldPath(TestConstants::CONTEXT_EMAIL, FieldMaskConfig::replace(TestConstants::MASK_EMAIL_BRACKETS));
$this->assertArrayHasKey(TestConstants::CONTEXT_EMAIL, $builder->getFieldPaths());
}
@@ -90,7 +90,7 @@ final class GdprProcessorBuilderTest extends TestCase
public function testAddFieldPaths(): void
{
$fieldPaths = [
TestConstants::CONTEXT_EMAIL => FieldMaskConfig::replace('[EMAIL]'),
TestConstants::CONTEXT_EMAIL => FieldMaskConfig::replace(TestConstants::MASK_EMAIL_BRACKETS),
TestConstants::CONTEXT_PASSWORD => FieldMaskConfig::remove(),
];
@@ -128,7 +128,7 @@ final class GdprProcessorBuilderTest extends TestCase
};
$processor = GdprProcessorBuilder::create()
->addFieldPath('field', FieldMaskConfig::replace('[MASKED]'))
->addFieldPath('field', FieldMaskConfig::replace(MaskConstants::MASK_BRACKETS))
->withAuditLogger($auditLogger)
->build();
@@ -229,7 +229,7 @@ final class GdprProcessorBuilderTest extends TestCase
public function getPatterns(): array
{
return ['/secret/' => '[SECRET]'];
return [TestConstants::PATTERN_SECRET => TestConstants::MASK_SECRET_BRACKETS];
}
};
@@ -297,7 +297,7 @@ final class GdprProcessorBuilderTest extends TestCase
public function getPatterns(): array
{
return ['/secret/' => '[SECRET]'];
return [TestConstants::PATTERN_SECRET => TestConstants::MASK_SECRET_BRACKETS];
}
};

View File

@@ -33,7 +33,7 @@ final class PluginAwareProcessorTest extends TestCase
};
$processor = GdprProcessorBuilder::create()
->addPattern('/TEST/', '[MASKED]')
->addPattern('/TEST/', MaskConstants::MASK_BRACKETS)
->addPlugin($plugin)
->buildWithPlugins();
@@ -48,7 +48,7 @@ final class PluginAwareProcessorTest extends TestCase
$result = $processor($record);
// Message should be uppercased, then 'TEST' should be masked
$this->assertStringContainsString('[MASKED]', $result->message);
$this->assertStringContainsString(TestConstants::MASK_MASKED_BRACKETS, $result->message);
}
public function testInvokeAppliesPostProcessing(): void
@@ -263,7 +263,7 @@ final class PluginAwareProcessorTest extends TestCase
->buildWithPlugins();
$this->assertInstanceOf(PluginAwareProcessor::class, $processor);
$this->assertSame(MaskConstants::MASK_GENERIC . ' message', $processor->regExpMessage('test message'));
$this->assertSame(MaskConstants::MASK_GENERIC . ' message', $processor->regExpMessage(TestConstants::MESSAGE_TEST_LOWERCASE));
}
public function testRecursiveMaskDelegates(): void
@@ -282,9 +282,9 @@ final class PluginAwareProcessorTest extends TestCase
$this->assertInstanceOf(PluginAwareProcessor::class, $processor);
$result = $processor->recursiveMask(['key' => 'test value']);
$result = $processor->recursiveMask(['key' => TestConstants::VALUE_TEST]);
$this->assertSame(MaskConstants::MASK_GENERIC . ' value', $result['key']);
$this->assertSame(MaskConstants::MASK_GENERIC . TestConstants::VALUE_SUFFIX, $result['key']);
}
public function testSetAuditLoggerDelegates(): void

View File

@@ -48,18 +48,18 @@ final class ConditionalRuleFactoryInstanceTest extends TestCase
public function testContextFieldRuleWithPresentField(): void
{
$factory = new ConditionalRuleFactory();
$rule = $factory->contextFieldRule('user_id');
$rule = $factory->contextFieldRule(TestConstants::CONTEXT_USER_ID);
$record = $this->createLogRecord('Test message', ['user_id' => 123]);
$record = $this->createLogRecord(TestConstants::MESSAGE_DEFAULT, [TestConstants::CONTEXT_USER_ID => 123]);
$this->assertTrue($rule($record));
}
public function testContextFieldRuleWithMissingField(): void
{
$factory = new ConditionalRuleFactory();
$rule = $factory->contextFieldRule('user_id');
$rule = $factory->contextFieldRule(TestConstants::CONTEXT_USER_ID);
$record = $this->createLogRecord('Test message', []);
$record = $this->createLogRecord(TestConstants::MESSAGE_DEFAULT, []);
$this->assertFalse($rule($record));
}
@@ -185,14 +185,14 @@ final class ConditionalRuleFactoryInstanceTest extends TestCase
$instanceFactory = new ConditionalRuleFactory();
// Create rules using both methods
$instanceFieldRule = $instanceFactory->contextFieldRule('user.email');
$staticFieldRule = ConditionalRuleFactory::createContextFieldRule('user.email');
$instanceFieldRule = $instanceFactory->contextFieldRule(TestConstants::FIELD_USER_EMAIL);
$staticFieldRule = ConditionalRuleFactory::createContextFieldRule(TestConstants::FIELD_USER_EMAIL);
$instanceValueRule = $instanceFactory->contextValueRule('type', 'admin');
$staticValueRule = ConditionalRuleFactory::createContextValueRule('type', 'admin');
$record = $this->createLogRecord('Test', [
'user' => ['email' => 'test@example.com'],
'user' => [TestConstants::CONTEXT_EMAIL => TestConstants::EMAIL_TEST],
'type' => 'admin',
]);

View File

@@ -63,7 +63,7 @@ final class ContextProcessorTest extends TestCase
{
$regexProcessor = fn(string $val): string => $val;
$processor = new ContextProcessor(
[TestConstants::CONTEXT_PASSWORD => FieldMaskConfig::replace('[REDACTED]')],
[TestConstants::CONTEXT_PASSWORD => FieldMaskConfig::replace(TestConstants::MASK_REDACTED_BRACKETS)],
[],
null,
$regexProcessor
@@ -73,7 +73,7 @@ final class ContextProcessorTest extends TestCase
$processed = $processor->maskFieldPaths($accessor);
$this->assertSame([TestConstants::CONTEXT_PASSWORD], $processed);
$this->assertSame('[REDACTED]', $accessor->get(TestConstants::CONTEXT_PASSWORD));
$this->assertSame(TestConstants::MASK_REDACTED_BRACKETS, $accessor->get(TestConstants::CONTEXT_PASSWORD));
}
public function testMaskFieldPathsSkipsNonExistentPaths(): void

View File

@@ -98,14 +98,14 @@ final class AuditLoggingExceptionComprehensiveTest extends TestCase
$value = ['data' => 'test'];
$exception = AuditLoggingException::serializationFailed(
'user.data',
TestConstants::FIELD_USER_DATA,
$value,
'JSON encoding failed'
);
$this->assertInstanceOf(AuditLoggingException::class, $exception);
$message = $exception->getMessage();
$this->assertStringContainsString('user.data', $message);
$this->assertStringContainsString(TestConstants::FIELD_USER_DATA, $message);
$this->assertStringContainsString('JSON encoding failed', $message);
$this->assertStringContainsString('serialization_failure', $message);
}

View File

@@ -228,13 +228,13 @@ class CustomExceptionsTest extends TestCase
public function testAuditLoggingExceptionSerializationFailed(): void
{
$exception = AuditLoggingException::serializationFailed(
'user.data',
TestConstants::FIELD_USER_DATA,
['circular' => 'reference'],
'Circular reference detected'
);
$this->assertStringContainsString(
"Audit data serialization failed for path 'user.data'",
"Audit data serialization failed for path '" . TestConstants::FIELD_USER_DATA . "'",
$exception->getMessage()
);
$this->assertStringContainsString('Circular reference detected', $exception->getMessage());

View File

@@ -7,6 +7,7 @@ namespace Tests\Exceptions;
use Ivuorinen\MonologGdprFilter\Exceptions\MaskingOperationFailedException;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\TestCase;
use Tests\TestConstants;
#[CoversClass(MaskingOperationFailedException::class)]
final class MaskingOperationFailedExceptionTest extends TestCase
@@ -77,7 +78,7 @@ final class MaskingOperationFailedExceptionTest extends TestCase
$previous = new \RuntimeException('Inner error');
$exception = MaskingOperationFailedException::fieldPathMaskingFailed(
'user.data',
TestConstants::FIELD_USER_DATA,
['complex' => 'value'],
'Failed',
$previous

View File

@@ -9,6 +9,7 @@ use Ivuorinen\MonologGdprFilter\Factory\AuditLoggerFactory;
use Ivuorinen\MonologGdprFilter\RateLimitedAuditLogger;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\TestCase;
use Tests\TestConstants;
#[CoversClass(AuditLoggerFactory::class)]
final class AuditLoggerFactoryTest extends TestCase
@@ -66,12 +67,12 @@ final class AuditLoggerFactoryTest extends TestCase
$storage = [];
$logger = $factory->createArrayLogger($storage);
$logger('test.path', 'original', 'masked');
$logger('test.path', 'original', TestConstants::DATA_MASKED);
$this->assertCount(1, $storage);
$this->assertSame('test.path', $storage[0]['path']);
$this->assertSame('original', $storage[0]['original']);
$this->assertSame('masked', $storage[0]['masked']);
$this->assertSame(TestConstants::DATA_MASKED, $storage[0][TestConstants::DATA_MASKED]);
$this->assertArrayHasKey('timestamp', $storage[0]);
}
@@ -90,7 +91,7 @@ final class AuditLoggerFactoryTest extends TestCase
$logger = $factory->createNullLogger();
// Should not throw
$logger('path', 'original', 'masked');
$logger('path', 'original', TestConstants::DATA_MASKED);
$this->assertTrue(true);
}
@@ -109,11 +110,11 @@ final class AuditLoggerFactoryTest extends TestCase
$factory = AuditLoggerFactory::create();
$calls = [];
$callback = function (string $path, mixed $original, mixed $masked) use (&$calls): void {
$calls[] = ['path' => $path, 'original' => $original, 'masked' => $masked];
$calls[] = ['path' => $path, 'original' => $original, TestConstants::DATA_MASKED => $masked];
};
$logger = $factory->createCallbackLogger($callback);
$logger('test.path', 'original', 'masked');
$logger('test.path', 'original', TestConstants::DATA_MASKED);
$this->assertCount(1, $calls);
$this->assertSame('test.path', $calls[0]['path']);

View File

@@ -175,7 +175,7 @@ final class GdprProcessorComprehensiveTest extends TestCase
// Should not throw exception
GdprProcessor::validatePatternsArray([
TestConstants::PATTERN_DIGITS => Mask::MASK_MASKED,
'/[a-z]+/' => Mask::MASK_REDACTED,
TestConstants::PATTERN_SAFE => Mask::MASK_REDACTED,
]);
$this->assertTrue(true);
@@ -202,7 +202,7 @@ final class GdprProcessorComprehensiveTest extends TestCase
$data = [
'level1' => [
'level2' => [
'value' => 'secret data',
'value' => TestConstants::MESSAGE_SECRET_DATA,
],
],
];

View File

@@ -59,10 +59,10 @@ final class GdprProcessorEdgeCasesTest extends TestCase
);
// Call maskMessage directly
$result = $processor->maskMessage('test value');
$result = $processor->maskMessage(TestConstants::VALUE_TEST);
// Should work normally
$this->assertSame(Mask::MASK_MASKED . ' value', $result);
$this->assertSame(Mask::MASK_MASKED . TestConstants::VALUE_SUFFIX, $result);
// Now test with patterns that might cause issues
// Note: It's hard to trigger preg_replace null return in normal usage
@@ -79,7 +79,7 @@ final class GdprProcessorEdgeCasesTest extends TestCase
// Test recursiveMask with array
$data = [
'level1' => [
'level2' => 'secret data',
'level2' => TestConstants::MESSAGE_SECRET_DATA,
],
];

View File

@@ -33,7 +33,7 @@ class GdprProcessorMethodsTest extends TestCase
];
$fieldPaths = [
TestConstants::FIELD_USER_EMAIL => FieldMaskConfig::useProcessorPatterns(),
'user.ssn' => FieldMaskConfig::remove(),
TestConstants::FIELD_USER_SSN => FieldMaskConfig::remove(),
'user.card' => FieldMaskConfig::replace('MASKED'),
];
$context = [
@@ -75,7 +75,7 @@ class GdprProcessorMethodsTest extends TestCase
{
$patterns = [];
$fieldPaths = [
'user.ssn' => FieldMaskConfig::remove(),
TestConstants::FIELD_USER_SSN => FieldMaskConfig::remove(),
];
$context = ['user' => ['ssn' => self::TEST_HETU]];

View File

@@ -58,7 +58,7 @@ class GdprProcessorTest extends TestCase
{
$patterns = DefaultPatterns::get();
$fieldPaths = [
'user.ssn' => FieldMaskConfig::remove(),
TestConstants::FIELD_USER_SSN => FieldMaskConfig::remove(),
];
$processor = $this->createProcessor($patterns, $fieldPaths);
$record = new LogRecord(
@@ -284,7 +284,7 @@ class GdprProcessorTest extends TestCase
$validPatterns = [
TestConstants::PATTERN_TEST => 'REPLACED',
TestConstants::PATTERN_DIGITS => 'NUMBER',
'/[a-z]+/' => 'LETTERS'
TestConstants::PATTERN_SAFE => 'LETTERS'
];
$processor = $this->createProcessor($validPatterns);

View File

@@ -70,9 +70,9 @@ class FieldMaskConfigValidationTest extends TestCase
public function regexMaskThrowsExceptionForIncompleteRegexPattern(): void
{
$this->expectException(InvalidRegexPatternException::class);
$this->expectExceptionMessage("Invalid regex pattern '/unclosed'");
$this->expectExceptionMessage("Invalid regex pattern '" . TestConstants::PATTERN_INVALID_UNCLOSED . "'");
FieldMaskConfig::regexMask('/unclosed');
FieldMaskConfig::regexMask(TestConstants::PATTERN_INVALID_UNCLOSED);
}
#[Test]
@@ -217,7 +217,7 @@ class FieldMaskConfigValidationTest extends TestCase
#[Test]
public function toArrayAndFromArrayRoundTripWorksCorrectly(): void
{
$original = FieldMaskConfig::replace('[REDACTED]');
$original = FieldMaskConfig::replace(TestConstants::MASK_REDACTED_BRACKETS);
$array = $original->toArray();
$restored = FieldMaskConfig::fromArray($array);

View File

@@ -144,7 +144,7 @@ class GdprProcessorValidationTest extends TestCase
$processor = new GdprProcessor([], [
TestConstants::FIELD_USER_EMAIL => FieldMaskConfig::remove(),
TestConstants::FIELD_USER_NAME => 'masked_value',
'payment.card' => FieldMaskConfig::replace('[CARD]')
'payment.card' => FieldMaskConfig::replace(TestConstants::MASK_CARD_BRACKETS)
]);
$this->assertInstanceOf(GdprProcessor::class, $processor);
@@ -429,7 +429,7 @@ class GdprProcessorValidationTest extends TestCase
{
$processor = new GdprProcessor([], [
TestConstants::FIELD_USER_EMAIL => FieldMaskConfig::remove(),
TestConstants::FIELD_USER_NAME => FieldMaskConfig::replace('[REDACTED]'),
TestConstants::FIELD_USER_NAME => FieldMaskConfig::replace(TestConstants::MASK_REDACTED_BRACKETS),
'user.phone' => FieldMaskConfig::regexMask('/\d/', '*'),
'metadata.ip' => 'simple_string_replacement'
]);

View File

@@ -84,7 +84,7 @@ final class InputValidatorTest extends TestCase
{
InputValidator::validatePatterns([
TestConstants::PATTERN_SSN_FORMAT => MaskConstants::MASK_SSN_PATTERN,
'/[a-z]+/' => 'REDACTED',
TestConstants::PATTERN_SAFE => TestConstants::MASK_REDACTED_PLAIN,
]);
$this->assertTrue(true);
@@ -140,7 +140,7 @@ final class InputValidatorTest extends TestCase
InputValidator::validateFieldPaths([
TestConstants::FIELD_USER_EMAIL => MaskConstants::MASK_EMAIL_PATTERN,
TestConstants::FIELD_USER_PASSWORD => FieldMaskConfig::remove(),
'user.ssn' => $ssnConfig,
TestConstants::FIELD_USER_SSN => $ssnConfig,
]);
$this->assertTrue(true);
@@ -297,7 +297,7 @@ final class InputValidatorTest extends TestCase
InputValidator::validateDataTypeMasks([
'integer' => MaskConstants::MASK_GENERIC,
'double' => MaskConstants::MASK_GENERIC,
'string' => 'REDACTED',
'string' => TestConstants::MASK_REDACTED_PLAIN,
'boolean' => MaskConstants::MASK_GENERIC,
'NULL' => 'null',
'array' => '[]',

View File

@@ -28,7 +28,7 @@ class JsonMaskingTest extends TestCase
TestConstants::PATTERN_EMAIL_FULL => MaskConstants::MASK_EMAIL
]);
$message = 'User data: {"email": "user@example.com", "name": "John Doe"}';
$message = 'User data: {"' . TestConstants::CONTEXT_EMAIL . '": "' . TestConstants::EMAIL_USER . '", "name": "' . TestConstants::NAME_FULL . '"}';
$result = $processor->regExpMessage($message);
$this->assertStringContainsString(MaskConstants::MASK_EMAIL, $result);
@@ -47,7 +47,7 @@ class JsonMaskingTest extends TestCase
TestConstants::PATTERN_EMAIL_FULL => MaskConstants::MASK_EMAIL
]);
$message = 'Users: [{"email": "admin@example.com"}, {"email": "user@test.com"}]';
$message = 'Users: [{"' . TestConstants::CONTEXT_EMAIL . '": "' . TestConstants::EMAIL_ADMIN . '"}, {"' . TestConstants::CONTEXT_EMAIL . '": "user@test.com"}]';
$result = $processor->regExpMessage($message);
$this->assertStringContainsString(MaskConstants::MASK_EMAIL, $result);
@@ -69,7 +69,7 @@ class JsonMaskingTest extends TestCase
]);
$message = 'Complex data: {"user": {"contact": '
. '{"email": "nested@example.com", "ssn": "' . TestConstants::SSN_US . '"}, "id": 42}}';
. '{"' . TestConstants::CONTEXT_EMAIL . '": "nested@example.com", "ssn": "' . TestConstants::SSN_US . '"}, "id": 42}}';
$result = $processor->regExpMessage($message);
$this->assertStringContainsString(MaskConstants::MASK_EMAIL, $result);
@@ -91,7 +91,7 @@ class JsonMaskingTest extends TestCase
TestConstants::PATTERN_EMAIL_FULL => MaskConstants::MASK_EMAIL
]);
$message = 'Request: {"email": "req@example.com"} Response: {"email": "resp@test.com", "status": "ok"}';
$message = 'Request: {"' . TestConstants::CONTEXT_EMAIL . '": "req@example.com"} Response: {"' . TestConstants::CONTEXT_EMAIL . '": "resp@test.com", "status": "ok"}';
$result = $processor->regExpMessage($message);
$this->assertStringContainsString(MaskConstants::MASK_EMAIL, $result);
@@ -135,7 +135,7 @@ class JsonMaskingTest extends TestCase
TestConstants::PATTERN_EMAIL_FULL => MaskConstants::MASK_EMAIL
]);
$message = 'Data: {"email": "user@example.com", "message": "Hello \"world\"", "unicode": "café ñoño"}';
$message = 'Data: {"' . TestConstants::CONTEXT_EMAIL . '": "' . TestConstants::EMAIL_USER . '", "' . TestConstants::FIELD_MESSAGE . '": "Hello \"world\"", "unicode": "café ñoño"}';
$result = $processor->regExpMessage($message);
$this->assertStringContainsString(MaskConstants::MASK_EMAIL, $result);
@@ -159,7 +159,7 @@ class JsonMaskingTest extends TestCase
['integer' => MaskConstants::MASK_INT, 'string' => MaskConstants::MASK_STRING]
);
$message = 'Data: {"email": "user@example.com", "id": 12345, "active": true}';
$message = 'Data: {"' . TestConstants::CONTEXT_EMAIL . '": "' . TestConstants::EMAIL_USER . '", "id": 12345, "active": true}';
$result = $processor->regExpMessage($message);
$extractedJson = $this->extractJsonFromMessage($result);
@@ -187,7 +187,7 @@ class JsonMaskingTest extends TestCase
$auditLogger
);
$message = 'User: {"email": "test@example.com", "name": "Test User"}';
$message = 'User: {"' . TestConstants::CONTEXT_EMAIL . '": "' . TestConstants::EMAIL_TEST . '", "name": "Test User"}';
$result = $processor->regExpMessage($message);
$this->assertStringContainsString(MaskConstants::MASK_EMAIL, $result);
@@ -218,7 +218,7 @@ class JsonMaskingTest extends TestCase
new DateTimeImmutable(),
'test',
Level::Info,
'API Response: {"user": {"email": "api@example.com"}, "status": "success"}',
'API Response: {"user": {"' . TestConstants::CONTEXT_EMAIL . '": "api@example.com"}, "status": "success"}',
[]
);
@@ -253,7 +253,7 @@ class JsonMaskingTest extends TestCase
new DateTimeImmutable(),
'test',
Level::Error,
'Error data: {"email": "error@example.com"}',
'Error data: {"' . TestConstants::CONTEXT_EMAIL . '": "error@example.com"}',
[]
);
@@ -266,7 +266,7 @@ class JsonMaskingTest extends TestCase
new DateTimeImmutable(),
'test',
Level::Info,
'Info data: {"email": "info@example.com"}',
'Info data: {"' . TestConstants::CONTEXT_EMAIL . '": "info@example.com"}',
[]
);
@@ -286,18 +286,18 @@ class JsonMaskingTest extends TestCase
"users": [
{
"id": 1,
"email": "john@example.com",
"' . TestConstants::CONTEXT_EMAIL . '": "' . TestConstants::EMAIL_JOHN . '",
"contacts": {
"phone": "' . TestConstants::PHONE_US . '",
"emergency": {
"email": "emergency@example.com",
"' . TestConstants::CONTEXT_EMAIL . '": "emergency@example.com",
"phone": "' . TestConstants::PHONE_US_ALT . '"
}
}
},
{
"id": 2,
"email": "jane@test.com",
"' . TestConstants::CONTEXT_EMAIL . '": "jane@test.com",
"contacts": {
"phone": "+1-555-456-7890"
}
@@ -310,7 +310,7 @@ class JsonMaskingTest extends TestCase
$this->assertStringContainsString(MaskConstants::MASK_EMAIL, $result);
$this->assertStringContainsString(MaskConstants::MASK_PHONE, $result);
$this->assertStringNotContainsString('john@example.com', $result);
$this->assertStringNotContainsString(TestConstants::EMAIL_JOHN, $result);
$this->assertStringNotContainsString('jane@test.com', $result);
$this->assertStringNotContainsString('emergency@example.com', $result);
$this->assertStringNotContainsString(TestConstants::PHONE_US, $result);
@@ -420,7 +420,7 @@ class JsonMaskingTest extends TestCase
TestConstants::PATTERN_EMAIL_FULL => MaskConstants::MASK_EMAIL
]);
$message = 'Data: {"email": "user@example.com", "optional": null, "empty": ""}';
$message = 'Data: {"' . TestConstants::CONTEXT_EMAIL . '": "' . TestConstants::EMAIL_USER . '", "optional": null, "empty": ""}';
$result = $processor->regExpMessage($message);
$extractedJson = $this->extractJsonFromMessage($result);

View File

@@ -25,7 +25,7 @@ final class MaskingOrchestratorTest extends TestCase
$result = $orchestrator->process('This is a test message', []);
$this->assertSame('This is a ' . MaskConstants::MASK_GENERIC . ' message', $result['message']);
$this->assertSame('This is a ' . MaskConstants::MASK_GENERIC . ' message', $result[TestConstants::FIELD_MESSAGE]);
$this->assertSame([], $result['context']);
}
@@ -35,9 +35,9 @@ final class MaskingOrchestratorTest extends TestCase
[TestConstants::PATTERN_TEST => MaskConstants::MASK_GENERIC]
);
$result = $orchestrator->process('message', ['key' => TestConstants::VALUE_TEST]);
$result = $orchestrator->process(TestConstants::FIELD_MESSAGE, ['key' => TestConstants::VALUE_TEST]);
$this->assertSame('message', $result['message']);
$this->assertSame(TestConstants::FIELD_MESSAGE, $result[TestConstants::FIELD_MESSAGE]);
$this->assertSame(MaskConstants::MASK_GENERIC . TestConstants::VALUE_SUFFIX, $result['context']['key']);
}
@@ -48,7 +48,7 @@ final class MaskingOrchestratorTest extends TestCase
[TestConstants::CONTEXT_EMAIL => FieldMaskConfig::replace(TestConstants::MASK_EMAIL_BRACKETS)]
);
$result = $orchestrator->process('message', [TestConstants::CONTEXT_EMAIL => TestConstants::EMAIL_TEST]);
$result = $orchestrator->process(TestConstants::FIELD_MESSAGE, [TestConstants::CONTEXT_EMAIL => TestConstants::EMAIL_TEST]);
$this->assertSame(TestConstants::MASK_EMAIL_BRACKETS, $result['context'][TestConstants::CONTEXT_EMAIL]);
}
@@ -61,7 +61,7 @@ final class MaskingOrchestratorTest extends TestCase
['name' => fn(mixed $val): string => strtoupper((string) $val)]
);
$result = $orchestrator->process('message', ['name' => 'john']);
$result = $orchestrator->process(TestConstants::FIELD_MESSAGE, ['name' => 'john']);
$this->assertSame('JOHN', $result['context']['name']);
}
@@ -80,12 +80,12 @@ final class MaskingOrchestratorTest extends TestCase
public function testRegExpMessageMasksPatterns(): void
{
$orchestrator = new MaskingOrchestrator(
[TestConstants::PATTERN_SSN_FORMAT => '[SSN]']
[TestConstants::PATTERN_SSN_FORMAT => TestConstants::MASK_SSN_BRACKETS]
);
$result = $orchestrator->regExpMessage('SSN: 123-45-6789');
$this->assertSame('SSN: [SSN]', $result);
$this->assertSame(TestConstants::EXPECTED_SSN_MASKED, $result);
}
public function testRegExpMessagePreservesEmptyString(): void
@@ -115,7 +115,7 @@ final class MaskingOrchestratorTest extends TestCase
[TestConstants::PATTERN_TEST => MaskConstants::MASK_GENERIC]
);
$result = $orchestrator->recursiveMask('test string');
$result = $orchestrator->recursiveMask(TestConstants::MESSAGE_TEST_STRING);
$this->assertSame(MaskConstants::MASK_GENERIC . ' string', $result);
}
@@ -136,7 +136,7 @@ final class MaskingOrchestratorTest extends TestCase
public function testCreateWithValidParameters(): void
{
$orchestrator = MaskingOrchestrator::create(
[TestConstants::PATTERN_DIGITS => '[DIGITS]'],
[TestConstants::PATTERN_DIGITS => TestConstants::MASK_DIGITS_BRACKETS],
[],
[],
null,
@@ -178,7 +178,7 @@ final class MaskingOrchestratorTest extends TestCase
$orchestrator = new MaskingOrchestrator(
[],
['field' => FieldMaskConfig::replace('[MASKED]')]
['field' => FieldMaskConfig::replace(MaskConstants::MASK_BRACKETS)]
);
$orchestrator->setAuditLogger($auditLogger);
@@ -218,11 +218,11 @@ final class MaskingOrchestratorTest extends TestCase
[
TestConstants::CONTEXT_EMAIL => TestConstants::EMAIL_TEST,
'name' => 'john',
'message' => 'test'
TestConstants::FIELD_MESSAGE => 'test'
]
);
$this->assertSame('Hello ' . MaskConstants::MASK_GENERIC, $result['message']);
$this->assertSame('Hello ' . MaskConstants::MASK_GENERIC, $result[TestConstants::FIELD_MESSAGE]);
$this->assertSame(TestConstants::MASK_EMAIL_BRACKETS, $result['context'][TestConstants::CONTEXT_EMAIL]);
$this->assertSame('JOHN', $result['context']['name']);
}
@@ -235,12 +235,12 @@ final class MaskingOrchestratorTest extends TestCase
[],
null,
100,
['integer' => '[INT]']
['integer' => TestConstants::MASK_INT_BRACKETS]
);
$result = $orchestrator->processContext(['count' => 42]);
$this->assertSame('[INT]', $result['count']);
$this->assertSame(TestConstants::MASK_INT_BRACKETS, $result['count']);
}
public function testProcessContextWithRemoveConfig(): void

View File

@@ -8,6 +8,7 @@ use Ivuorinen\MonologGdprFilter\Contracts\MaskingPluginInterface;
use Ivuorinen\MonologGdprFilter\Plugins\AbstractMaskingPlugin;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\TestCase;
use Tests\TestConstants;
#[CoversClass(AbstractMaskingPlugin::class)]
final class AbstractMaskingPluginTest extends TestCase
@@ -92,7 +93,7 @@ final class AbstractMaskingPluginTest extends TestCase
}
};
$message = 'test message';
$message = TestConstants::MESSAGE_TEST_LOWERCASE;
$result = $plugin->preProcessMessage($message);
$this->assertSame($message, $result);
@@ -107,7 +108,7 @@ final class AbstractMaskingPluginTest extends TestCase
}
};
$message = 'test message';
$message = TestConstants::MESSAGE_TEST_LOWERCASE;
$result = $plugin->postProcessMessage($message);
$this->assertSame($message, $result);
@@ -185,13 +186,13 @@ final class AbstractMaskingPluginTest extends TestCase
public function getPatterns(): array
{
return ['/secret/' => '[REDACTED]'];
return [TestConstants::PATTERN_SECRET => TestConstants::MASK_REDACTED_BRACKETS];
}
};
$patterns = $plugin->getPatterns();
$this->assertArrayHasKey('/secret/', $patterns);
$this->assertSame('[REDACTED]', $patterns['/secret/']);
$this->assertArrayHasKey(TestConstants::PATTERN_SECRET, $patterns);
$this->assertSame(TestConstants::MASK_REDACTED_BRACKETS, $patterns[TestConstants::PATTERN_SECRET]);
}
}

View File

@@ -41,9 +41,9 @@ final class RecoveryResultTest extends TestCase
public function testFallbackCreation(): void
{
$error = ErrorContext::create('TestError', 'Failed to mask');
$result = RecoveryResult::fallback('[REDACTED]', 3, $error, 50.0);
$result = RecoveryResult::fallback(TestConstants::MASK_REDACTED_BRACKETS, 3, $error, 50.0);
$this->assertSame('[REDACTED]', $result->value);
$this->assertSame(TestConstants::MASK_REDACTED_BRACKETS, $result->value);
$this->assertSame(RecoveryResult::OUTCOME_FALLBACK, $result->outcome);
$this->assertSame(3, $result->attempts);
$this->assertSame($error, $result->lastError);

View File

@@ -286,7 +286,7 @@ final class RetryStrategyTest extends TestCase
$auditLogs[] = [
'path' => $path,
'original' => $original,
'masked' => $masked
TestConstants::DATA_MASKED => $masked
];
};

View File

@@ -115,7 +115,7 @@ final class RecursiveProcessorTest extends TestCase
$dataTypeMasker = new DataTypeMasker([]);
$processor = new RecursiveProcessor($regexProcessor, $dataTypeMasker, null, 10);
$data = ['field1' => 'secret data', 'field2' => TestConstants::DATA_PUBLIC];
$data = ['field1' => TestConstants::MESSAGE_SECRET_DATA, 'field2' => TestConstants::DATA_PUBLIC];
$result = $processor->processStandardArray($data, 0);
$this->assertSame('*** data', $result['field1']);
@@ -128,7 +128,7 @@ final class RecursiveProcessorTest extends TestCase
$dataTypeMasker = new DataTypeMasker([]);
$processor = new RecursiveProcessor($regexProcessor, $dataTypeMasker, null, 10);
$result = $processor->processValue('test string', 0);
$result = $processor->processValue(TestConstants::MESSAGE_TEST_STRING, 0);
$this->assertSame('*** string', $result);
}
@@ -158,7 +158,7 @@ final class RecursiveProcessorTest extends TestCase
public function testProcessStringValueWithRegexMatch(): void
{
$regexProcessor = fn(string $val): string => str_replace(TestConstants::CONTEXT_PASSWORD, '[REDACTED]', $val);
$regexProcessor = fn(string $val): string => str_replace(TestConstants::CONTEXT_PASSWORD, TestConstants::MASK_REDACTED_BRACKETS, $val);
$dataTypeMasker = new DataTypeMasker([]);
$processor = new RecursiveProcessor($regexProcessor, $dataTypeMasker, null, 10);
@@ -198,7 +198,7 @@ final class RecursiveProcessorTest extends TestCase
$dataTypeMasker = new DataTypeMasker([]);
$processor = new RecursiveProcessor($regexProcessor, $dataTypeMasker, null, 10);
$result = $processor->processArrayValue(['key' => 'secret data'], 0);
$result = $processor->processArrayValue(['key' => TestConstants::MESSAGE_SECRET_DATA], 0);
$this->assertIsArray($result);
$this->assertSame('*** data', $result['key']);

View File

@@ -40,7 +40,7 @@ class RegexMaskProcessorTest extends TestCase
"/\b\d{6}[-+A]?\d{3}[A-Z]\b/u" => Mask::MASK_MASKED,
];
$fieldPaths = [
"user.ssn" => self::GDPR_REPLACEMENT,
TestConstants::FIELD_USER_SSN => self::GDPR_REPLACEMENT,
"order.total" => FieldMaskConfig::useProcessorPatterns(),
];
$this->processor = new GdprProcessor($patterns, $fieldPaths);
@@ -49,7 +49,7 @@ class RegexMaskProcessorTest extends TestCase
public function testRemoveFieldRemovesKey(): void
{
$patterns = DefaultPatterns::get();
$fieldPaths = ["user.ssn" => FieldMaskConfig::remove()];
$fieldPaths = [TestConstants::FIELD_USER_SSN => FieldMaskConfig::remove()];
$processor = new GdprProcessor($patterns, $fieldPaths);
$record = $this->logEntry()->with(
message: "Remove SSN",
@@ -177,7 +177,7 @@ class RegexMaskProcessorTest extends TestCase
foreach ($testHetu as $hetu) {
$record = $this->logEntry()->with(message: 'ID: ' . $hetu);
$result = ($this->processor)($record)->toArray();
$this->assertSame("ID: " . Mask::MASK_MASKED, $result["message"]);
$this->assertSame("ID: " . Mask::MASK_MASKED, $result[TestConstants::FIELD_MESSAGE]);
}
}
@@ -208,7 +208,7 @@ class RegexMaskProcessorTest extends TestCase
context: ["user" => ["ssn" => "not-a-hetu"]],
);
$result = ($this->processor)($record)->toArray();
$this->assertSame("No sensitive data here", $result["message"]);
$this->assertSame("No sensitive data here", $result[TestConstants::FIELD_MESSAGE]);
$this->assertSame(self::GDPR_REPLACEMENT, $result["context"]["user"]["ssn"]);
}

View File

@@ -126,9 +126,6 @@ class ComprehensiveValidationTest extends TestCase
$this->assertInstanceOf(LogRecord::class, $result);
$this->assertArrayHasKey('test_value', $result->context);
// Log successful processing for each type
error_log('✅ Successfully processed PHP type: ' . $typeName);
}
$this->assertCount(
@@ -158,7 +155,6 @@ class ComprehensiveValidationTest extends TestCase
$rateLimiter->isAllowed('memory_test_key_' . $i);
}
memory_get_usage(true);
$initialStats = RateLimiter::getMemoryStats();
// Phase 2: Wait for cleanup window and trigger cleanup
@@ -196,12 +192,6 @@ class ComprehensiveValidationTest extends TestCase
$cleanupStats['total_keys'],
'Keys should not accumulate indefinitely'
);
error_log(sprintf(
'✅ Memory management working: Keys before=%d, after=%d',
$initialStats['total_keys'],
$cleanupStats['total_keys']
));
}
/**
@@ -226,50 +216,27 @@ class ComprehensiveValidationTest extends TestCase
];
$caughtCount = 0;
$totalPatterns = count($definitelyDangerousPatterns) + count($possiblyDangerousPatterns);
// Test definitely dangerous patterns
foreach ($definitelyDangerousPatterns as $pattern => $description) {
foreach (array_keys($definitelyDangerousPatterns) as $pattern) {
try {
PatternValidator::validateAll([sprintf('/%s/', $pattern) => TestConstants::DATA_MASKED]);
error_log(sprintf(
'⚠️ Pattern not caught: %s (%s)',
$pattern,
$description
));
} catch (Throwable) {
$caughtCount++;
error_log(sprintf(
'✅ Caught dangerous pattern: %s (%s)',
$pattern,
$description
));
}
}
// Test possibly dangerous patterns (implementation may vary)
foreach ($possiblyDangerousPatterns as $pattern => $description) {
foreach (array_keys($possiblyDangerousPatterns) as $pattern) {
try {
PatternValidator::validateAll([sprintf('/%s/', $pattern) => TestConstants::DATA_MASKED]);
error_log(sprintf(
' Pattern allowed: %s (%s)',
$pattern,
$description
));
} catch (Throwable) {
$caughtCount++;
error_log(sprintf(
'✅ Caught potentially dangerous pattern: %s (%s)',
$pattern,
$description
));
}
}
// At least some dangerous patterns should be caught
$this->assertGreaterThan(0, $caughtCount, 'ReDoS protection should catch at least some dangerous patterns');
error_log(sprintf('✅ ReDoS protection caught %d/%d dangerous patterns', $caughtCount, $totalPatterns));
}
/**
@@ -367,17 +334,10 @@ class ComprehensiveValidationTest extends TestCase
}
if ($sensitiveTermsFound !== []) {
error_log(sprintf(
"⚠️ Scenario '%s': Sensitive terms still present: ",
$scenario
) . implode(', ', $sensitiveTermsFound));
error_log(
' Full message: ' . $loggedMessage
);
} else {
error_log(sprintf(
"✅ Scenario '%s': No sensitive terms found in sanitized message",
$scenario
$this->fail(sprintf(
"Scenario '%s': %d sensitive term(s) still present in masked output",
$scenario,
count($sensitiveTermsFound)
));
}
@@ -424,8 +384,6 @@ class ComprehensiveValidationTest extends TestCase
if ($json === false) {
$this->fail('RateLimiter::getMemoryStats() returned false');
}
error_log("✅ Rate limiter statistics: " . $json);
}
/**
@@ -484,19 +442,8 @@ class ComprehensiveValidationTest extends TestCase
$memoryIncrease,
'Memory usage should be reasonable for ' . $name
);
error_log(sprintf(
"✅ Safely processed extreme value '%s' in %ss using %d bytes",
$name,
$processingTime,
$memoryIncrease
));
} catch (Throwable $e) {
// Some extreme values might cause controlled exceptions
error_log(sprintf(
" Extreme value '%s' caused controlled exception: ",
$name
) . $e->getMessage());
$this->assertInstanceOf(Throwable::class, $e);
}
}
@@ -528,8 +475,8 @@ class ComprehensiveValidationTest extends TestCase
$processor = $this->createProcessor(
patterns: [
'/\b\d{3}-\d{2}-\d{4}\b/' => MaskConstants::MASK_USSSN,
'/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/' => MaskConstants::MASK_EMAIL,
'/\b\d{4}[\s-]?\d{4}[\s-]?\d{4}[\s-]?\d{4}\b/' => MaskConstants::MASK_CC,
TestConstants::PATTERN_EMAIL_SIMPLE => MaskConstants::MASK_EMAIL,
TestConstants::PATTERN_CREDIT_CARD => MaskConstants::MASK_CC,
],
fieldPaths: [
TestConstants::FIELD_USER_PASSWORD => FieldMaskConfig::remove(),
@@ -618,11 +565,6 @@ class ComprehensiveValidationTest extends TestCase
// Rate limiter should provide stats
$stats = $rateLimitedLogger->getRateLimitStats();
$this->assertIsArray($stats);
error_log(
"✅ Complete integration test passed with "
. count($this->auditLog) . " audit log entries"
);
}
/**
@@ -646,9 +588,6 @@ class ComprehensiveValidationTest extends TestCase
PatternValidator::clearCache();
RateLimiter::clearAll();
// Log final validation summary
error_log("🎯 Comprehensive validation completed successfully");
parent::tearDown();
}
}

View File

@@ -84,7 +84,7 @@ class CriticalBugRegressionTest extends TestCase
$testCases = [
'integer' => 42,
'double' => 3.14,
'string' => 'test string',
'string' => TestConstants::MESSAGE_TEST_STRING,
'boolean_true' => true,
'boolean_false' => false,
'null' => null,
@@ -378,7 +378,7 @@ class CriticalBugRegressionTest extends TestCase
{
$safePatterns = [
'/\b\d{3}-\d{2}-\d{4}\b/' => 'SSN',
'/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/' => 'EMAIL',
TestConstants::PATTERN_EMAIL_SIMPLE => 'EMAIL',
'/\b\d{4}\s?\d{4}\s?\d{4}\s?\d{4}\b/' => 'CREDIT_CARD',
'/\+?1?[-.\s]?\(?(\d{3})\)?[-.\s]?(\d{3})[-.\s]?(\d{4})/' => 'PHONE',
];
@@ -467,23 +467,6 @@ class CriticalBugRegressionTest extends TestCase
// Note: Current implementation may not fully sanitize all patterns
$this->assertStringContainsString('Rule error:', (string) $errorMessage);
// Test that at least some sanitization occurs (implementation-dependent)
$containsSensitiveInfo = false;
$sensitiveTerms = ['password=secret123', 'user=secret_user', 'host=sensitive.db.com'];
foreach ($sensitiveTerms as $term) {
if (str_contains((string) $errorMessage, $term)) {
$containsSensitiveInfo = true;
break;
}
}
// If sensitive info is still present, log a warning for future improvement
if ($containsSensitiveInfo) {
error_log(
"Warning: Error message sanitization may need improvement: " . $errorMessage
);
}
// For now, just ensure the error was logged properly
$this->assertNotEmpty($errorMessage);
}

View File

@@ -74,8 +74,8 @@ class SecurityRegressionTest extends TestCase
{
$redosPatterns = [
// Nested quantifiers - classic ReDoS
'/^(a+)+$/',
'/^(a*)*$/',
TestConstants::PATTERN_REDOS_VULNERABLE,
TestConstants::PATTERN_REDOS_NESTED_STAR,
'/^(a+)*$/',
// Alternation with overlapping
@@ -124,8 +124,8 @@ class SecurityRegressionTest extends TestCase
$legitimatePatterns = [
// Common GDPR patterns
'/\b\d{3}-\d{2}-\d{4}\b/' => 'SSN',
'/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/' => 'EMAIL',
'/\b\d{4}[\s-]?\d{4}[\s-]?\d{4}[\s-]?\d{4}\b/' => 'CREDIT_CARD',
TestConstants::PATTERN_EMAIL_SIMPLE => MaskConstants::MASK_EMAIL,
TestConstants::PATTERN_CREDIT_CARD => 'CREDIT_CARD',
'/\+?1?[-.\s]?\(?(\d{3})\)?[-.\s]?(\d{3})[-.\s]?(\d{4})/' => 'PHONE',
'/\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b/' => 'IP_ADDRESS',

View File

@@ -7,6 +7,7 @@ namespace Tests\Retention;
use Ivuorinen\MonologGdprFilter\Retention\RetentionPolicy;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\TestCase;
use Tests\TestConstants;
#[CoversClass(RetentionPolicy::class)]
final class RetentionPolicyTest extends TestCase
@@ -48,9 +49,9 @@ final class RetentionPolicyTest extends TestCase
public function testGetFieldsCustom(): void
{
$policy = new RetentionPolicy('test', 30, RetentionPolicy::ACTION_DELETE, ['email', 'phone']);
$policy = new RetentionPolicy('test', 30, RetentionPolicy::ACTION_DELETE, [TestConstants::CONTEXT_EMAIL, 'phone']);
$this->assertSame(['email', 'phone'], $policy->getFields());
$this->assertSame([TestConstants::CONTEXT_EMAIL, 'phone'], $policy->getFields());
}
public function testIsWithinRetentionRecent(): void
@@ -119,12 +120,12 @@ final class RetentionPolicyTest extends TestCase
public function testAnonymizeFactory(): void
{
$policy = RetentionPolicy::anonymize('user_data', 90, ['email', 'name']);
$policy = RetentionPolicy::anonymize('user_data', 90, [TestConstants::CONTEXT_EMAIL, 'name']);
$this->assertSame('user_data', $policy->getName());
$this->assertSame(90, $policy->getRetentionDays());
$this->assertSame(RetentionPolicy::ACTION_ANONYMIZE, $policy->getAction());
$this->assertSame(['email', 'name'], $policy->getFields());
$this->assertSame([TestConstants::CONTEXT_EMAIL, 'name'], $policy->getFields());
}
public function testActionConstants(): void

View File

@@ -6,6 +6,7 @@ namespace Tests;
use Ivuorinen\MonologGdprFilter\MaskConstants;
use Ivuorinen\MonologGdprFilter\SecuritySanitizer;
use Tests\TestConstants;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\Test;
@@ -314,10 +315,10 @@ class SecuritySanitizerTest extends TestCase
#[Test]
public function preservesPublicIpAddresses(): void
{
$message = 'External server at 8.8.8.8 responded';
$message = 'External server at ' . TestConstants::IP_ADDRESS_PUBLIC . ' responded';
$sanitized = SecuritySanitizer::sanitizeErrorMessage($message);
$this->assertStringContainsString('8.8.8.8', $sanitized);
$this->assertStringContainsString(TestConstants::IP_ADDRESS_PUBLIC, $sanitized);
}
#[Test]

View File

@@ -43,7 +43,7 @@ final class SerializedDataProcessorTest extends TestCase
{
$processor = $this->createProcessor();
$message = 'User data: {"email":"' . TestConstants::EMAIL_JOHN . '","name":"John"}';
$message = 'User data: {"' . TestConstants::CONTEXT_EMAIL . '":"' . TestConstants::EMAIL_JOHN . '","name":"' . TestConstants::NAME_FIRST . '"}';
$result = $processor->process($message);
$this->assertStringContainsString(MaskConstants::MASK_EMAIL, $result);
@@ -54,7 +54,7 @@ final class SerializedDataProcessorTest extends TestCase
{
$processor = $this->createProcessor();
$message = 'Data: {"id":123,"email":"' . TestConstants::EMAIL_TEST . '"}';
$message = 'Data: {"id":123,"' . TestConstants::CONTEXT_EMAIL . '":"' . TestConstants::EMAIL_TEST . '"}';
$result = $processor->process($message);
// Should still be valid JSON in the message
@@ -70,7 +70,7 @@ final class SerializedDataProcessorTest extends TestCase
{
$processor = $this->createProcessor();
$message = 'User: {"user":{"contact":{"email":"' . TestConstants::EMAIL_TEST . '"}}}';
$message = 'User: {"user":{"contact":{"' . TestConstants::CONTEXT_EMAIL . '":"' . TestConstants::EMAIL_TEST . '"}}}';
$result = $processor->process($message);
$this->assertStringContainsString(MaskConstants::MASK_EMAIL, $result);
@@ -83,8 +83,8 @@ final class SerializedDataProcessorTest extends TestCase
$printROutput = 'Array
(
[name] => John Doe
[email] => ' . TestConstants::EMAIL_JOHN . '
[name] => ' . TestConstants::NAME_FULL . '
[' . TestConstants::CONTEXT_EMAIL . '] => ' . TestConstants::EMAIL_JOHN . '
[age] => 30
)';
@@ -92,7 +92,7 @@ final class SerializedDataProcessorTest extends TestCase
$this->assertStringContainsString(MaskConstants::MASK_EMAIL, $result);
$this->assertStringNotContainsString(TestConstants::EMAIL_JOHN, $result);
$this->assertStringContainsString('John Doe', $result); // Name not masked
$this->assertStringContainsString(TestConstants::NAME_FULL, $result); // Name not masked
}
public function testProcessPrintROutputWithNestedArrays(): void
@@ -119,8 +119,8 @@ PRINT_R;
$processor = $this->createProcessor();
$varExportOutput = "array (
'name' => 'John Doe',
'email' => '" . TestConstants::EMAIL_JOHN . "',
'name' => '" . TestConstants::NAME_FULL . "',
'" . TestConstants::CONTEXT_EMAIL . "' => '" . TestConstants::EMAIL_JOHN . "',
'active' => true,
)";
@@ -134,7 +134,7 @@ PRINT_R;
{
$processor = $this->createProcessor();
$data = ['email' => TestConstants::EMAIL_TEST, 'name' => 'Test'];
$data = [TestConstants::CONTEXT_EMAIL => TestConstants::EMAIL_TEST, 'name' => 'Test'];
$serialized = serialize($data);
$result = $processor->process($serialized);
@@ -162,7 +162,7 @@ PRINT_R;
{
$processor = $this->createProcessor();
$message = 'Log entry: User {"email":"' . TestConstants::EMAIL_TEST . '"} performed action';
$message = 'Log entry: User {"' . TestConstants::CONTEXT_EMAIL . '":"' . TestConstants::EMAIL_TEST . '"} performed action';
$result = $processor->process($message);
$this->assertStringContainsString('Log entry: User', $result);
@@ -179,7 +179,7 @@ PRINT_R;
$processor = $this->createProcessor($auditLogger);
$processor->process('{"email":"' . TestConstants::EMAIL_TEST . '"}');
$processor->process('{"' . TestConstants::CONTEXT_EMAIL . '":"' . TestConstants::EMAIL_TEST . '"}');
$this->assertNotEmpty($logs);
$this->assertStringContainsString('json', $logs[0]['path']);
@@ -195,7 +195,7 @@ PRINT_R;
$processor = $this->createProcessor();
$processor->setAuditLogger($auditLogger);
$processor->process('{"email":"' . TestConstants::EMAIL_TEST . '"}');
$processor->process('{"' . TestConstants::CONTEXT_EMAIL . '":"' . TestConstants::EMAIL_TEST . '"}');
$this->assertNotEmpty($logs);
}
@@ -214,7 +214,7 @@ PRINT_R;
{
$processor = $this->createProcessor();
$message = 'Users: [{"email":"a@example.com"},{"email":"b@example.com"}]';
$message = 'Users: [{"' . TestConstants::CONTEXT_EMAIL . '":"a@example.com"},{"' . TestConstants::CONTEXT_EMAIL . '":"b@example.com"}]';
$result = $processor->process($message);
$this->assertStringNotContainsString('a@example.com', $result);
@@ -237,7 +237,7 @@ PRINT_R;
$processor = $this->createProcessor();
$varExportOutput = 'array (
"email" => "' . TestConstants::EMAIL_JOHN . '",
"' . TestConstants::CONTEXT_EMAIL . '" => "' . TestConstants::EMAIL_JOHN . '",
)';
$result = $processor->process($varExportOutput);
@@ -249,7 +249,7 @@ PRINT_R;
{
$processor = $this->createProcessor();
$message = 'JSON: {"email":"a@example.com"} and serialized: s:16:"b@example.com";';
$message = 'JSON: {"' . TestConstants::CONTEXT_EMAIL . '":"a@example.com"} and serialized: s:16:"b@example.com";';
$result = $processor->process($message);
$this->assertStringNotContainsString('a@example.com', $result);
@@ -258,13 +258,13 @@ PRINT_R;
public function testProcessWithCustomMasker(): void
{
$customMasker = fn(string $value): string => str_replace('secret', '[REDACTED]', $value);
$customMasker = fn(string $value): string => str_replace('secret', TestConstants::MASK_REDACTED_BRACKETS, $value);
$processor = new SerializedDataProcessor($customMasker);
$message = '{"data":"this is secret information"}';
$result = $processor->process($message);
$this->assertStringContainsString('[REDACTED]', $result);
$this->assertStringContainsString(TestConstants::MASK_REDACTED_BRACKETS, $result);
$this->assertStringNotContainsString('secret', $result);
}
}

View File

@@ -58,7 +58,7 @@ final class AbstractMaskingStrategyTest extends TestCase
*/
public function getName(): string
{
return 'Test Strategy';
return TestConstants::STRATEGY_TEST;
}
/**
@@ -123,8 +123,8 @@ final class AbstractMaskingStrategyTest extends TestCase
#[Test]
public function valueToStringConvertsStringAsIs(): void
{
$result = $this->strategy->testValueToString('test string');
$this->assertSame('test string', $result);
$result = $this->strategy->testValueToString(TestConstants::MESSAGE_TEST_STRING);
$this->assertSame(TestConstants::MESSAGE_TEST_STRING, $result);
}
#[Test]
@@ -159,7 +159,7 @@ final class AbstractMaskingStrategyTest extends TestCase
public function valueToStringConvertsArray(): void
{
$result = $this->strategy->testValueToString(['key' => 'value']);
$this->assertSame('{"key":"value"}', $result);
$this->assertSame(TestConstants::JSON_KEY_VALUE, $result);
}
#[Test]
@@ -256,7 +256,7 @@ final class AbstractMaskingStrategyTest extends TestCase
$conditions = [
'level' => 'Error',
'channel' => 'test-channel',
'message' => TestConstants::MESSAGE_TEST_LOWERCASE,
TestConstants::FIELD_MESSAGE => TestConstants::MESSAGE_TEST_LOWERCASE,
TestConstants::CONTEXT_USER_ID => 123,
];
@@ -323,7 +323,7 @@ final class AbstractMaskingStrategyTest extends TestCase
public function generateValuePreviewHandlesNonStringValues(): void
{
$preview = $this->strategy->testGenerateValuePreview(['key' => 'value']);
$this->assertSame('{"key":"value"}', $preview);
$this->assertSame(TestConstants::JSON_KEY_VALUE, $preview);
}
#[Test]
@@ -383,8 +383,8 @@ final class AbstractMaskingStrategyTest extends TestCase
#[Test]
public function preserveValueTypeConvertsBackToArray(): void
{
$result = $this->strategy->testPreserveValueType(['original' => 'value'], '{"masked":"data"}');
$this->assertSame(['masked' => 'data'], $result);
$result = $this->strategy->testPreserveValueType(['original' => 'value'], '{"' . TestConstants::DATA_MASKED . '":"data"}');
$this->assertSame([TestConstants::DATA_MASKED => 'data'], $result);
$this->assertIsArray($result);
}
@@ -392,10 +392,10 @@ final class AbstractMaskingStrategyTest extends TestCase
public function preserveValueTypeConvertsBackToObject(): void
{
$original = (object) ['original' => 'value'];
$result = $this->strategy->testPreserveValueType($original, '{"masked":"data"}');
$result = $this->strategy->testPreserveValueType($original, '{"' . TestConstants::DATA_MASKED . '":"data"}');
$this->assertIsObject($result);
$this->assertEquals((object) ['masked' => 'data'], $result);
$this->assertEquals((object) [TestConstants::DATA_MASKED => 'data'], $result);
}
#[Test]

View File

@@ -5,6 +5,7 @@ declare(strict_types=1);
namespace Tests\Strategies;
use Ivuorinen\MonologGdprFilter\Exceptions\MaskingOperationFailedException;
use Ivuorinen\MonologGdprFilter\MaskConstants;
use Ivuorinen\MonologGdprFilter\Exceptions\RuleExecutionException;
use Ivuorinen\MonologGdprFilter\Strategies\CallbackMaskingStrategy;
use PHPUnit\Framework\TestCase;
@@ -23,9 +24,9 @@ final class CallbackMaskingStrategyTest extends TestCase
public function testBasicConstruction(): void
{
$callback = fn(mixed $value): string => TestConstants::MASK_MASKED_BRACKETS;
$strategy = new CallbackMaskingStrategy('user.email', $callback);
$strategy = new CallbackMaskingStrategy(TestConstants::FIELD_USER_EMAIL, $callback);
$this->assertSame('user.email', $strategy->getFieldPath());
$this->assertSame(TestConstants::FIELD_USER_EMAIL, $strategy->getFieldPath());
$this->assertTrue($strategy->isExactMatch());
$this->assertSame(50, $strategy->getPriority());
}
@@ -33,10 +34,10 @@ final class CallbackMaskingStrategyTest extends TestCase
public function testMaskWithSimpleCallback(): void
{
$callback = fn(mixed $value): string => TestConstants::MASK_MASKED_BRACKETS;
$strategy = new CallbackMaskingStrategy('user.email', $callback);
$strategy = new CallbackMaskingStrategy(TestConstants::FIELD_USER_EMAIL, $callback);
$record = $this->createLogRecord();
$result = $strategy->mask('john@example.com', 'user.email', $record);
$result = $strategy->mask(TestConstants::EMAIL_JOHN, TestConstants::FIELD_USER_EMAIL, $record);
$this->assertSame(TestConstants::MASK_MASKED_BRACKETS, $result);
}
@@ -44,10 +45,10 @@ final class CallbackMaskingStrategyTest extends TestCase
public function testMaskWithTransformingCallback(): void
{
$callback = fn(mixed $value): string => strtoupper((string) $value);
$strategy = new CallbackMaskingStrategy('user.name', $callback);
$strategy = new CallbackMaskingStrategy(TestConstants::FIELD_USER_NAME, $callback);
$record = $this->createLogRecord();
$result = $strategy->mask('john', 'user.name', $record);
$result = $strategy->mask('john', TestConstants::FIELD_USER_NAME, $record);
$this->assertSame('JOHN', $result);
}
@@ -57,27 +58,27 @@ final class CallbackMaskingStrategyTest extends TestCase
$callback = function (): never {
throw new RuleExecutionException('Callback failed');
};
$strategy = new CallbackMaskingStrategy('user.data', $callback);
$strategy = new CallbackMaskingStrategy(TestConstants::FIELD_USER_DATA, $callback);
$record = $this->createLogRecord();
$this->expectException(MaskingOperationFailedException::class);
$this->expectExceptionMessage('Callback threw exception');
$strategy->mask('value', 'user.data', $record);
$strategy->mask('value', TestConstants::FIELD_USER_DATA, $record);
}
public function testShouldApplyWithExactMatch(): void
{
$callback = fn(mixed $value): string => TestConstants::MASK_MASKED_BRACKETS;
$strategy = new CallbackMaskingStrategy(
'user.email',
TestConstants::FIELD_USER_EMAIL,
$callback,
exactMatch: true
);
$record = $this->createLogRecord();
$this->assertTrue($strategy->shouldApply('value', 'user.email', $record));
$this->assertFalse($strategy->shouldApply('value', 'user.name', $record));
$this->assertTrue($strategy->shouldApply('value', TestConstants::FIELD_USER_EMAIL, $record));
$this->assertFalse($strategy->shouldApply('value', TestConstants::FIELD_USER_NAME, $record));
$this->assertFalse($strategy->shouldApply('value', 'user.email.work', $record));
}
@@ -85,32 +86,32 @@ final class CallbackMaskingStrategyTest extends TestCase
{
$callback = fn(mixed $value): string => TestConstants::MASK_MASKED_BRACKETS;
$strategy = new CallbackMaskingStrategy(
'user.*',
TestConstants::PATH_USER_WILDCARD,
$callback,
exactMatch: false
);
$record = $this->createLogRecord();
$this->assertTrue($strategy->shouldApply('value', 'user.email', $record));
$this->assertTrue($strategy->shouldApply('value', 'user.name', $record));
$this->assertTrue($strategy->shouldApply('value', TestConstants::FIELD_USER_EMAIL, $record));
$this->assertTrue($strategy->shouldApply('value', TestConstants::FIELD_USER_NAME, $record));
$this->assertFalse($strategy->shouldApply('value', 'admin.email', $record));
}
public function testGetName(): void
{
$callback = fn(mixed $value): string => TestConstants::MASK_MASKED_BRACKETS;
$strategy = new CallbackMaskingStrategy('user.email', $callback);
$strategy = new CallbackMaskingStrategy(TestConstants::FIELD_USER_EMAIL, $callback);
$name = $strategy->getName();
$this->assertStringContainsString('Callback Masking', $name);
$this->assertStringContainsString('user.email', $name);
$this->assertStringContainsString(TestConstants::FIELD_USER_EMAIL, $name);
}
public function testValidate(): void
{
$callback = fn(mixed $value): string => TestConstants::MASK_MASKED_BRACKETS;
$strategy = new CallbackMaskingStrategy('user.email', $callback);
$strategy = new CallbackMaskingStrategy(TestConstants::FIELD_USER_EMAIL, $callback);
$this->assertTrue($strategy->validate());
}
@@ -119,7 +120,7 @@ final class CallbackMaskingStrategyTest extends TestCase
{
$callback = fn(mixed $value): string => TestConstants::MASK_MASKED_BRACKETS;
$strategy = new CallbackMaskingStrategy(
'user.email',
TestConstants::FIELD_USER_EMAIL,
$callback,
75,
false
@@ -130,7 +131,7 @@ final class CallbackMaskingStrategyTest extends TestCase
$this->assertArrayHasKey('field_path', $config);
$this->assertArrayHasKey('exact_match', $config);
$this->assertArrayHasKey('priority', $config);
$this->assertSame('user.email', $config['field_path']);
$this->assertSame(TestConstants::FIELD_USER_EMAIL, $config['field_path']);
$this->assertFalse($config['exact_match']);
$this->assertSame(75, $config['priority']);
}
@@ -138,7 +139,7 @@ final class CallbackMaskingStrategyTest extends TestCase
public function testForPathsFactoryMethod(): void
{
$callback = fn(mixed $value): string => TestConstants::MASK_MASKED_BRACKETS;
$paths = ['user.email', 'admin.email', 'contact.email'];
$paths = [TestConstants::FIELD_USER_EMAIL, 'admin.email', 'contact.email'];
$strategies = CallbackMaskingStrategy::forPaths($paths, $callback);
@@ -152,20 +153,20 @@ final class CallbackMaskingStrategyTest extends TestCase
public function testConstantFactoryMethod(): void
{
$strategy = CallbackMaskingStrategy::constant('user.ssn', '***-**-****');
$strategy = CallbackMaskingStrategy::constant(TestConstants::FIELD_USER_SSN, MaskConstants::MASK_SSN_PATTERN);
$record = $this->createLogRecord();
$result = $strategy->mask('123-45-6789', 'user.ssn', $record);
$result = $strategy->mask(TestConstants::SSN_US, TestConstants::FIELD_USER_SSN, $record);
$this->assertSame('***-**-****', $result);
$this->assertSame(MaskConstants::MASK_SSN_PATTERN, $result);
}
public function testHashFactoryMethod(): void
{
$strategy = CallbackMaskingStrategy::hash('user.password', 'sha256', 8);
$strategy = CallbackMaskingStrategy::hash(TestConstants::FIELD_USER_PASSWORD, 'sha256', 8);
$record = $this->createLogRecord();
$result = $strategy->mask('secret123', 'user.password', $record);
$result = $strategy->mask('secret123', TestConstants::FIELD_USER_PASSWORD, $record);
$this->assertIsString($result);
$this->assertSame(11, strlen($result));
@@ -174,24 +175,24 @@ final class CallbackMaskingStrategyTest extends TestCase
public function testHashWithNoTruncation(): void
{
$strategy = CallbackMaskingStrategy::hash('user.password', 'md5', 0);
$strategy = CallbackMaskingStrategy::hash(TestConstants::FIELD_USER_PASSWORD, 'md5', 0);
$record = $this->createLogRecord();
$result = $strategy->mask('test', 'user.password', $record);
$result = $strategy->mask('test', TestConstants::FIELD_USER_PASSWORD, $record);
$this->assertSame(32, strlen((string) $result));
}
public function testPartialFactoryMethod(): void
{
$strategy = CallbackMaskingStrategy::partial('user.email', 2, 4);
$strategy = CallbackMaskingStrategy::partial(TestConstants::FIELD_USER_EMAIL, 2, 4);
$record = $this->createLogRecord();
$result = $strategy->mask('john@example.com', 'user.email', $record);
$result = $strategy->mask(TestConstants::EMAIL_JOHN, TestConstants::FIELD_USER_EMAIL, $record);
$this->assertStringStartsWith('jo', $result);
$this->assertStringEndsWith('.com', $result);
$this->assertStringContainsString('***', $result);
$this->assertStringContainsString(MaskConstants::MASK_GENERIC, $result);
}
public function testPartialWithShortString(): void
@@ -201,7 +202,7 @@ final class CallbackMaskingStrategyTest extends TestCase
$result = $strategy->mask('abc', 'user.code', $record);
$this->assertSame('***', $result);
$this->assertSame(MaskConstants::MASK_GENERIC, $result);
}
public function testPartialWithCustomMaskChar(): void
@@ -224,29 +225,29 @@ final class CallbackMaskingStrategyTest extends TestCase
return TestConstants::MASK_MASKED_BRACKETS;
};
$strategy = new CallbackMaskingStrategy('user.data', $callback);
$strategy = new CallbackMaskingStrategy(TestConstants::FIELD_USER_DATA, $callback);
$record = $this->createLogRecord();
$strategy->mask(['key' => 'value'], 'user.data', $record);
$strategy->mask(['key' => 'value'], TestConstants::FIELD_USER_DATA, $record);
$this->assertSame($receivedValue, ['key' => 'value']);
}
public function testCallbackCanReturnNonString(): void
{
$callback = fn(mixed $value): array => ['masked' => true];
$strategy = new CallbackMaskingStrategy('user.data', $callback);
$callback = fn(mixed $value): array => [TestConstants::DATA_MASKED => true];
$strategy = new CallbackMaskingStrategy(TestConstants::FIELD_USER_DATA, $callback);
$record = $this->createLogRecord();
$result = $strategy->mask(['key' => 'value'], 'user.data', $record);
$result = $strategy->mask(['key' => 'value'], TestConstants::FIELD_USER_DATA, $record);
$this->assertSame(['masked' => true], $result);
$this->assertSame([TestConstants::DATA_MASKED => true], $result);
}
public function testCustomPriority(): void
{
$callback = fn(mixed $value): string => TestConstants::MASK_MASKED_BRACKETS;
$strategy = new CallbackMaskingStrategy('user.email', $callback, 100);
$strategy = new CallbackMaskingStrategy(TestConstants::FIELD_USER_EMAIL, $callback, 100);
$this->assertSame(100, $strategy->getPriority());
}

View File

@@ -59,7 +59,7 @@ final class ConditionalMaskingStrategyComprehensiveTest extends TestCase
public function getName(): string
{
return 'Test Strategy';
return TestConstants::STRATEGY_TEST;
}
public function validate(): bool

View File

@@ -146,14 +146,14 @@ final class DataTypeMaskingStrategyComprehensiveTest extends TestCase
public function testMaskWithObjectValueJsonMask(): void
{
$strategy = new DataTypeMaskingStrategy(['object' => '{"masked":"data"}']);
$strategy = new DataTypeMaskingStrategy(['object' => '{"' . TestConstants::DATA_MASKED . '":"data"}']);
$record = $this->createLogRecord('Test');
$obj = (object)['original' => 'value'];
$result = $strategy->mask($obj, 'field', $record);
$expected = (object)['masked' => 'data'];
$expected = (object)[TestConstants::DATA_MASKED => 'data'];
$this->assertEquals($expected, $result);
}
@@ -411,7 +411,7 @@ final class DataTypeMaskingStrategyComprehensiveTest extends TestCase
'double' => 'D',
'boolean' => '1', // Boolean uses filter_var, so '1' becomes true
'array' => '["MASKED"]', // JSON array
'object' => '{"masked":"value"}', // JSON object
'object' => '{"' . TestConstants::DATA_MASKED . '":"value"}', // JSON object
'NULL' => 'N',
]);
@@ -422,7 +422,7 @@ final class DataTypeMaskingStrategyComprehensiveTest extends TestCase
$this->assertSame('D', $strategy->mask(3.14, 'f', $record));
$this->assertTrue($strategy->mask(true, 'f', $record)); // Boolean conversion
$this->assertSame(['MASKED'], $strategy->mask([], 'f', $record));
$this->assertEquals((object)['masked' => 'value'], $strategy->mask((object)[], 'f', $record));
$this->assertEquals((object)[TestConstants::DATA_MASKED => 'value'], $strategy->mask((object)[], 'f', $record));
$this->assertSame('N', $strategy->mask(null, 'f', $record));
}
}

View File

@@ -70,7 +70,7 @@ final class DataTypeMaskingStrategyTest extends TestCase
{
$strategy = new DataTypeMaskingStrategy(['string' => MaskConstants::MASK_GENERIC]);
$this->assertTrue($strategy->shouldApply('test string', 'field', $this->logRecord));
$this->assertTrue($strategy->shouldApply(TestConstants::MESSAGE_TEST_STRING, 'field', $this->logRecord));
}
#[Test]
@@ -107,11 +107,11 @@ final class DataTypeMaskingStrategyTest extends TestCase
#[Test]
public function maskAppliesStringMask(): void
{
$strategy = new DataTypeMaskingStrategy(['string' => 'REDACTED']);
$strategy = new DataTypeMaskingStrategy(['string' => TestConstants::MASK_REDACTED_PLAIN]);
$result = $strategy->mask('sensitive data', 'field', $this->logRecord);
$this->assertSame('REDACTED', $result);
$this->assertSame(TestConstants::MASK_REDACTED_PLAIN, $result);
}
#[Test]
@@ -177,7 +177,7 @@ final class DataTypeMaskingStrategyTest extends TestCase
#[Test]
public function maskAppliesArrayMaskJsonArray(): void
{
$strategy = new DataTypeMaskingStrategy(['array' => '["masked"]']);
$strategy = new DataTypeMaskingStrategy(['array' => '["' . TestConstants::DATA_MASKED . '"]']);
$result = $strategy->mask(['original'], 'field', $this->logRecord);
@@ -209,13 +209,13 @@ final class DataTypeMaskingStrategyTest extends TestCase
#[Test]
public function maskAppliesObjectMaskJsonObject(): void
{
$strategy = new DataTypeMaskingStrategy(['object' => '{"masked":"data"}']);
$strategy = new DataTypeMaskingStrategy(['object' => '{"' . TestConstants::DATA_MASKED . '":"data"}']);
$obj = (object) ['original' => 'value'];
$result = $strategy->mask($obj, 'field', $this->logRecord);
$this->assertIsObject($result);
$this->assertEquals((object) ['masked' => 'data'], $result);
$this->assertEquals((object) [TestConstants::DATA_MASKED => 'data'], $result);
}
#[Test]
@@ -227,7 +227,7 @@ final class DataTypeMaskingStrategyTest extends TestCase
$result = $strategy->mask($obj, 'field', $this->logRecord);
$this->assertIsObject($result);
$this->assertEquals((object) ['masked' => 'MASKED'], $result);
$this->assertEquals((object) [TestConstants::DATA_MASKED => 'MASKED'], $result);
}
#[Test]

View File

@@ -30,7 +30,7 @@ final class FieldPathMaskingStrategyEnhancedTest extends TestCase
$this->expectException(MaskingOperationFailedException::class);
$this->expectExceptionMessage('Regex pattern is null');
$strategy->mask('test value', 'field', $record);
$strategy->mask(TestConstants::VALUE_TEST, 'field', $record);
}
public function testApplyStaticReplacementWithNullReplacement(): void

View File

@@ -138,9 +138,9 @@ final class FieldPathMaskingStrategyTest extends TestCase
TestConstants::PATTERN_SSN_FORMAT,
MaskConstants::MASK_SSN_PATTERN
);
$strategy = new FieldPathMaskingStrategy(['user.ssn' => $ssnConfig]);
$strategy = new FieldPathMaskingStrategy([TestConstants::FIELD_USER_SSN => $ssnConfig]);
$result = $strategy->mask(TestConstants::SSN_US, 'user.ssn', $this->logRecord);
$result = $strategy->mask(TestConstants::SSN_US, TestConstants::FIELD_USER_SSN, $this->logRecord);
$this->assertSame(MaskConstants::MASK_SSN_PATTERN, $result);
}
@@ -149,12 +149,12 @@ final class FieldPathMaskingStrategyTest extends TestCase
public function maskAppliesStaticReplacementFromConfig(): void
{
$strategy = new FieldPathMaskingStrategy([
TestConstants::FIELD_USER_NAME => FieldMaskConfig::replace('[REDACTED]'),
TestConstants::FIELD_USER_NAME => FieldMaskConfig::replace(TestConstants::MASK_REDACTED_BRACKETS),
]);
$result = $strategy->mask(TestConstants::NAME_FULL, TestConstants::FIELD_USER_NAME, $this->logRecord);
$this->assertSame('[REDACTED]', $result);
$this->assertSame(TestConstants::MASK_REDACTED_BRACKETS, $result);
}
#[Test]
@@ -238,7 +238,7 @@ final class FieldPathMaskingStrategyTest extends TestCase
$strategy = new FieldPathMaskingStrategy([
TestConstants::FIELD_USER_EMAIL => MaskConstants::MASK_EMAIL_PATTERN,
TestConstants::FIELD_USER_PASSWORD => FieldMaskConfig::remove(),
'user.ssn' => $ssnConfig,
TestConstants::FIELD_USER_SSN => $ssnConfig,
]);
$this->assertTrue($strategy->validate());
@@ -328,7 +328,7 @@ final class FieldPathMaskingStrategyTest extends TestCase
public function maskHandlesMultipleReplacementsInSameValue(): void
{
$strategy = new FieldPathMaskingStrategy([
'message' => FieldMaskConfig::regexMask(TestConstants::PATTERN_SSN_FORMAT, MaskConstants::MASK_SSN_PATTERN),
TestConstants::FIELD_MESSAGE => FieldMaskConfig::regexMask(TestConstants::PATTERN_SSN_FORMAT, MaskConstants::MASK_SSN_PATTERN),
]);
$input = 'SSNs: 123-45-6789 and 987-65-4321';

View File

@@ -127,11 +127,11 @@ class MaskingStrategiesTest extends TestCase
$this->assertSame(80, $strategy->getPriority());
// Test shouldApply
$this->assertTrue($strategy->shouldApply('john@example.com', TestConstants::FIELD_USER_EMAIL, $logRecord));
$this->assertTrue($strategy->shouldApply(TestConstants::EMAIL_JOHN, TestConstants::FIELD_USER_EMAIL, $logRecord));
$this->assertFalse($strategy->shouldApply('some value', 'other.field', $logRecord));
// Test static replacement
$masked = $strategy->mask('john@example.com', TestConstants::FIELD_USER_EMAIL, $logRecord);
$masked = $strategy->mask(TestConstants::EMAIL_JOHN, TestConstants::FIELD_USER_EMAIL, $logRecord);
$this->assertEquals(MaskConstants::MASK_EMAIL, $masked);
// Test removal (returns null)
@@ -336,7 +336,7 @@ class MaskingStrategiesTest extends TestCase
#[\Override]
public function getName(): string
{
return 'Test Strategy';
return TestConstants::STRATEGY_TEST;
}
// Expose protected methods for testing

View File

@@ -64,7 +64,7 @@ final class RegexMaskingStrategyTest extends TestCase
$this->expectException(InvalidRegexPatternException::class);
$this->expectExceptionMessage('catastrophic backtracking');
new RegexMaskingStrategy(['/^(a+)+$/' => MaskConstants::MASK_GENERIC]);
new RegexMaskingStrategy([TestConstants::PATTERN_REDOS_VULNERABLE => MaskConstants::MASK_GENERIC]);
}
#[Test]
@@ -124,7 +124,7 @@ final class RegexMaskingStrategyTest extends TestCase
public function maskHandlesArrayValues(): void
{
$strategy = new RegexMaskingStrategy([
'/"email":"[^"]+"/' => '"email":"' . MaskConstants::MASK_EMAIL_PATTERN . '"',
'/"' . TestConstants::CONTEXT_EMAIL . '":"[^"]+"/' => '"' . TestConstants::CONTEXT_EMAIL . '":"' . MaskConstants::MASK_EMAIL_PATTERN . '"',
]);
$input = [TestConstants::CONTEXT_EMAIL => TestConstants::EMAIL_TEST];
@@ -208,12 +208,12 @@ final class RegexMaskingStrategyTest extends TestCase
{
$strategy = new RegexMaskingStrategy(
patterns: [TestConstants::PATTERN_DIGITS => MaskConstants::MASK_GENERIC],
includePaths: ['user.ssn', 'user.phone']
includePaths: [TestConstants::FIELD_USER_SSN, 'user.phone']
);
$this->assertTrue($strategy->shouldApply(
TestConstants::DATA_NUMBER_STRING,
'user.ssn',
TestConstants::FIELD_USER_SSN,
$this->logRecord
));
$this->assertTrue($strategy->shouldApply(
@@ -236,7 +236,7 @@ final class RegexMaskingStrategyTest extends TestCase
includePaths: [TestConstants::PATH_USER_WILDCARD]
);
$this->assertTrue($strategy->shouldApply(TestConstants::DATA_NUMBER_STRING, 'user.ssn', $this->logRecord));
$this->assertTrue($strategy->shouldApply(TestConstants::DATA_NUMBER_STRING, TestConstants::FIELD_USER_SSN, $this->logRecord));
$this->assertTrue($strategy->shouldApply(TestConstants::DATA_NUMBER_STRING, 'user.phone', $this->logRecord));
$this->assertFalse($strategy->shouldApply(TestConstants::DATA_NUMBER_STRING, 'admin.id', $this->logRecord));
}
@@ -277,7 +277,7 @@ final class RegexMaskingStrategyTest extends TestCase
{
$strategy = new RegexMaskingStrategy([
TestConstants::PATTERN_SSN_FORMAT => MaskConstants::MASK_SSN_PATTERN,
'/[a-z]+/' => 'REDACTED',
TestConstants::PATTERN_SAFE => TestConstants::MASK_REDACTED_PLAIN,
]);
$this->assertTrue($strategy->validate());
@@ -297,7 +297,7 @@ final class RegexMaskingStrategyTest extends TestCase
public function getConfigurationReturnsFullConfiguration(): void
{
$patterns = [TestConstants::PATTERN_DIGITS => MaskConstants::MASK_GENERIC];
$includePaths = ['user.ssn'];
$includePaths = [TestConstants::FIELD_USER_SSN];
$excludePaths = ['debug.*'];
$strategy = new RegexMaskingStrategy(
@@ -334,7 +334,7 @@ final class RegexMaskingStrategyTest extends TestCase
'/REPLACED/' => 'FINAL',
]);
$result = $strategy->mask('test value', 'field', $this->logRecord);
$result = $strategy->mask(TestConstants::VALUE_TEST, 'field', $this->logRecord);
$this->assertSame('FINAL value', $result);
}
@@ -367,13 +367,13 @@ final class RegexMaskingStrategyTest extends TestCase
public function maskHandlesMultilinePatterns(): void
{
$strategy = new RegexMaskingStrategy([
'/^line\d+$/m' => 'REDACTED',
'/^line\d+$/m' => TestConstants::MASK_REDACTED_PLAIN,
]);
$input = "line1\nother\nline2";
$result = $strategy->mask($input, 'field', $this->logRecord);
$this->assertStringContainsString('REDACTED', $result);
$this->assertStringContainsString(TestConstants::MASK_REDACTED_PLAIN, $result);
$this->assertStringContainsString('other', $result);
}
}

View File

@@ -17,6 +17,7 @@ use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;
use Tests\TestConstants;
/**
* Edge case tests for masking strategies to improve coverage.
@@ -35,7 +36,7 @@ final class StrategyEdgeCasesTest extends TestCase
datetime: new DateTimeImmutable(),
channel: 'test',
level: Level::Info,
message: 'Test message',
message: TestConstants::MESSAGE_DEFAULT,
context: [],
);
}
@@ -61,8 +62,8 @@ final class StrategyEdgeCasesTest extends TestCase
public static function redosPatternProvider(): array
{
return [
'nested plus quantifier' => ['/^(a+)+$/'],
'nested star quantifier' => ['/^(a*)*$/'],
'nested plus quantifier' => [TestConstants::PATTERN_REDOS_VULNERABLE],
'nested star quantifier' => [TestConstants::PATTERN_REDOS_NESTED_STAR],
'plus with repetition' => ['/^(a+){1,10}$/'],
'star with repetition' => ['/^(a*){1,10}$/'],
'identical alternation with star' => ['/(.*|.*)x/'],
@@ -76,9 +77,9 @@ final class StrategyEdgeCasesTest extends TestCase
public function regexStrategySafePatternsPasses(): void
{
$strategy = new RegexMaskingStrategy([
'/\d{3}-\d{2}-\d{4}/' => '[SSN]',
'/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/' => '[EMAIL]',
'/\b\d{4}[\s-]?\d{4}[\s-]?\d{4}[\s-]?\d{4}\b/' => '[CARD]',
TestConstants::PATTERN_SSN_FORMAT => TestConstants::MASK_SSN_BRACKETS,
TestConstants::PATTERN_EMAIL_SIMPLE => TestConstants::MASK_EMAIL_BRACKETS,
TestConstants::PATTERN_CREDIT_CARD => TestConstants::MASK_CARD_BRACKETS,
]);
$this->assertInstanceOf(RegexMaskingStrategy::class, $strategy);
@@ -153,7 +154,7 @@ final class StrategyEdgeCasesTest extends TestCase
$result = $strategy->mask($obj, 'field', $this->logRecord);
$this->assertIsObject($result);
$this->assertEquals((object) ['masked' => '{invalid json'], $result);
$this->assertEquals((object) [TestConstants::DATA_MASKED => '{invalid json'], $result);
}
#[Test]
@@ -165,7 +166,7 @@ final class StrategyEdgeCasesTest extends TestCase
$result = $strategy->mask($obj, 'field', $this->logRecord);
$this->assertIsObject($result);
$this->assertEquals((object) ['masked' => '["array"]'], $result);
$this->assertEquals((object) [TestConstants::DATA_MASKED => '["array"]'], $result);
}
#[Test]
@@ -256,7 +257,7 @@ final class StrategyEdgeCasesTest extends TestCase
public function fieldPathStrategyValidateWithValidStringConfig(): void
{
$strategy = new FieldPathMaskingStrategy([
'user.email' => MaskConstants::MASK_EMAIL_PATTERN,
TestConstants::FIELD_USER_EMAIL => MaskConstants::MASK_EMAIL_PATTERN,
]);
$this->assertTrue($strategy->validate());
@@ -266,8 +267,8 @@ final class StrategyEdgeCasesTest extends TestCase
public function fieldPathStrategyValidateWithFieldMaskConfig(): void
{
$strategy = new FieldPathMaskingStrategy([
'user.email' => FieldMaskConfig::replace(MaskConstants::MASK_EMAIL_PATTERN),
'user.ssn' => FieldMaskConfig::remove(),
TestConstants::FIELD_USER_EMAIL => FieldMaskConfig::replace(MaskConstants::MASK_EMAIL_PATTERN),
TestConstants::FIELD_USER_SSN => FieldMaskConfig::remove(),
]);
$this->assertTrue($strategy->validate());
@@ -277,7 +278,7 @@ final class StrategyEdgeCasesTest extends TestCase
public function fieldPathStrategyValidateWithValidRegexConfig(): void
{
$strategy = new FieldPathMaskingStrategy([
'user.data' => FieldMaskConfig::regexMask('/\d+/', '[MASKED]'),
TestConstants::FIELD_USER_DATA => FieldMaskConfig::regexMask(TestConstants::PATTERN_DIGITS, MaskConstants::MASK_BRACKETS),
]);
$this->assertTrue($strategy->validate());
@@ -338,7 +339,7 @@ final class StrategyEdgeCasesTest extends TestCase
public function fieldPathStrategyShouldApplyReturnsFalseForMissingPath(): void
{
$strategy = new FieldPathMaskingStrategy([
'user.email' => MaskConstants::MASK_EMAIL_PATTERN,
TestConstants::FIELD_USER_EMAIL => MaskConstants::MASK_EMAIL_PATTERN,
]);
$this->assertFalse($strategy->shouldApply('value', 'other.path', $this->logRecord));
@@ -348,11 +349,11 @@ final class StrategyEdgeCasesTest extends TestCase
public function fieldPathStrategyShouldApplyReturnsTrueForWildcardMatch(): void
{
$strategy = new FieldPathMaskingStrategy([
'user.*' => MaskConstants::MASK_GENERIC,
TestConstants::PATH_USER_WILDCARD => MaskConstants::MASK_GENERIC,
]);
$this->assertTrue($strategy->shouldApply('value', 'user.email', $this->logRecord));
$this->assertTrue($strategy->shouldApply('value', 'user.name', $this->logRecord));
$this->assertTrue($strategy->shouldApply('value', TestConstants::FIELD_USER_EMAIL, $this->logRecord));
$this->assertTrue($strategy->shouldApply('value', TestConstants::FIELD_USER_NAME, $this->logRecord));
}
#[Test]
@@ -371,19 +372,19 @@ final class StrategyEdgeCasesTest extends TestCase
public function fieldPathStrategyMaskAppliesRegexConfig(): void
{
$strategy = new FieldPathMaskingStrategy([
'user.ssn' => FieldMaskConfig::regexMask('/\d{3}-\d{2}-\d{4}/', '[SSN]'),
TestConstants::FIELD_USER_SSN => FieldMaskConfig::regexMask(TestConstants::PATTERN_SSN_FORMAT, TestConstants::MASK_SSN_BRACKETS),
]);
$result = $strategy->mask('SSN: 123-45-6789', 'user.ssn', $this->logRecord);
$result = $strategy->mask('SSN: 123-45-6789', TestConstants::FIELD_USER_SSN, $this->logRecord);
$this->assertSame('SSN: [SSN]', $result);
$this->assertSame(TestConstants::EXPECTED_SSN_MASKED, $result);
}
#[Test]
public function fieldPathStrategyMaskHandlesArrayValue(): void
{
$strategy = new FieldPathMaskingStrategy([
'data' => FieldMaskConfig::regexMask('/\d+/', '[NUM]'),
'data' => FieldMaskConfig::regexMask(TestConstants::PATTERN_DIGITS, '[NUM]'),
]);
$result = $strategy->mask(['count' => '123 items'], 'data', $this->logRecord);
@@ -396,7 +397,7 @@ final class StrategyEdgeCasesTest extends TestCase
public function fieldPathStrategyMaskReturnsValueWhenNoConfigMatch(): void
{
$strategy = new FieldPathMaskingStrategy([
'user.email' => MaskConstants::MASK_EMAIL_PATTERN,
TestConstants::FIELD_USER_EMAIL => MaskConstants::MASK_EMAIL_PATTERN,
]);
$result = $strategy->mask('original', 'other.field', $this->logRecord);
@@ -408,7 +409,7 @@ final class StrategyEdgeCasesTest extends TestCase
public function fieldPathStrategyGetNameReturnsCorrectFormat(): void
{
$strategy = new FieldPathMaskingStrategy([
'user.email' => MaskConstants::MASK_EMAIL_PATTERN,
TestConstants::FIELD_USER_EMAIL => MaskConstants::MASK_EMAIL_PATTERN,
'user.phone' => MaskConstants::MASK_PHONE,
]);
@@ -421,7 +422,7 @@ final class StrategyEdgeCasesTest extends TestCase
public function fieldPathStrategyGetConfigurationReturnsAllSettings(): void
{
$config = [
'user.email' => FieldMaskConfig::replace('[EMAIL]'),
TestConstants::FIELD_USER_EMAIL => FieldMaskConfig::replace(TestConstants::MASK_EMAIL_BRACKETS),
];
$strategy = new FieldPathMaskingStrategy($config);

View File

@@ -122,9 +122,9 @@ final class StrategyManagerComprehensiveTest extends TestCase
$manager = new StrategyManager();
$record = $this->createLogRecord('Test');
$result = $manager->maskValue('test value', 'field', $record);
$result = $manager->maskValue(TestConstants::VALUE_TEST, 'field', $record);
$this->assertSame('test value', $result);
$this->assertSame(TestConstants::VALUE_TEST, $result);
}
public function testMaskValueAppliesFirstApplicableStrategy(): void
@@ -137,7 +137,7 @@ final class StrategyManagerComprehensiveTest extends TestCase
$manager = new StrategyManager([$lowPrio, $highPrio]);
$record = $this->createLogRecord('Test');
$result = $manager->maskValue('secret data', 'field', $record);
$result = $manager->maskValue(TestConstants::MESSAGE_SECRET_DATA, 'field', $record);
// High priority strategy should be applied
$this->assertStringContainsString('HIGH', $result);
@@ -206,7 +206,7 @@ final class StrategyManagerComprehensiveTest extends TestCase
$manager = new StrategyManager([$strategy]);
$record = $this->createLogRecord('Test');
$result = $manager->hasApplicableStrategy('secret data', 'field', $record);
$result = $manager->hasApplicableStrategy(TestConstants::MESSAGE_SECRET_DATA, 'field', $record);
$this->assertTrue($result);
}

View File

@@ -25,13 +25,13 @@ final class StreamingProcessorTest extends TestCase
$processor = new StreamingProcessor($this->createOrchestrator(), 10);
$records = [
['message' => 'test message', 'context' => []],
[TestConstants::FIELD_MESSAGE => TestConstants::MESSAGE_TEST_LOWERCASE, 'context' => []],
];
$results = iterator_to_array($processor->processStream($records));
$this->assertCount(1, $results);
$this->assertSame(MaskConstants::MASK_GENERIC . ' message', $results[0]['message']);
$this->assertSame(MaskConstants::MASK_GENERIC . ' message', $results[0][TestConstants::FIELD_MESSAGE]);
}
public function testProcessStreamMultipleRecords(): void
@@ -39,9 +39,9 @@ final class StreamingProcessorTest extends TestCase
$processor = new StreamingProcessor($this->createOrchestrator(), 10);
$records = [
['message' => 'test one', 'context' => []],
['message' => 'test two', 'context' => []],
['message' => 'test three', 'context' => []],
[TestConstants::FIELD_MESSAGE => 'test one', 'context' => []],
[TestConstants::FIELD_MESSAGE => 'test two', 'context' => []],
[TestConstants::FIELD_MESSAGE => 'test three', 'context' => []],
];
$results = iterator_to_array($processor->processStream($records));
@@ -54,11 +54,11 @@ final class StreamingProcessorTest extends TestCase
$processor = new StreamingProcessor($this->createOrchestrator(), 2);
$records = [
['message' => 'test 1', 'context' => []],
['message' => 'test 2', 'context' => []],
['message' => 'test 3', 'context' => []],
['message' => 'test 4', 'context' => []],
['message' => 'test 5', 'context' => []],
[TestConstants::FIELD_MESSAGE => 'test 1', 'context' => []],
[TestConstants::FIELD_MESSAGE => 'test 2', 'context' => []],
[TestConstants::FIELD_MESSAGE => 'test 3', 'context' => []],
[TestConstants::FIELD_MESSAGE => 'test 4', 'context' => []],
[TestConstants::FIELD_MESSAGE => 'test 5', 'context' => []],
];
$results = iterator_to_array($processor->processStream($records));
@@ -71,12 +71,12 @@ final class StreamingProcessorTest extends TestCase
$processor = new StreamingProcessor($this->createOrchestrator(), 10);
$records = [
['message' => 'message', 'context' => ['key' => 'test value']],
[TestConstants::FIELD_MESSAGE => TestConstants::FIELD_MESSAGE, 'context' => ['key' => TestConstants::VALUE_TEST]],
];
$results = iterator_to_array($processor->processStream($records));
$this->assertSame(MaskConstants::MASK_GENERIC . ' value', $results[0]['context']['key']);
$this->assertSame(MaskConstants::MASK_GENERIC . TestConstants::VALUE_SUFFIX, $results[0]['context']['key']);
}
public function testProcessStreamWithGenerator(): void
@@ -84,9 +84,9 @@ final class StreamingProcessorTest extends TestCase
$processor = new StreamingProcessor($this->createOrchestrator(), 2);
$generator = (function () {
yield ['message' => 'test a', 'context' => []];
yield ['message' => 'test b', 'context' => []];
yield ['message' => 'test c', 'context' => []];
yield [TestConstants::FIELD_MESSAGE => 'test a', 'context' => []];
yield [TestConstants::FIELD_MESSAGE => 'test b', 'context' => []];
yield [TestConstants::FIELD_MESSAGE => 'test c', 'context' => []];
})();
$results = iterator_to_array($processor->processStream($generator));
@@ -104,7 +104,7 @@ final class StreamingProcessorTest extends TestCase
file_put_contents($tempFile, "test line 1\ntest line 2\ntest line 3\n");
try {
$lineParser = fn(string $line): array => ['message' => $line, 'context' => []];
$lineParser = fn(string $line): array => [TestConstants::FIELD_MESSAGE => $line, 'context' => []];
$results = [];
foreach ($processor->processFile($tempFile, $lineParser) as $result) {
@@ -112,7 +112,7 @@ final class StreamingProcessorTest extends TestCase
}
$this->assertCount(3, $results);
$this->assertStringContainsString(MaskConstants::MASK_GENERIC, $results[0]['message']);
$this->assertStringContainsString(MaskConstants::MASK_GENERIC, $results[0][TestConstants::FIELD_MESSAGE]);
} finally {
unlink($tempFile);
}
@@ -127,7 +127,7 @@ final class StreamingProcessorTest extends TestCase
file_put_contents($tempFile, "test line 1\n\n\ntest line 2\n");
try {
$lineParser = fn(string $line): array => ['message' => $line, 'context' => []];
$lineParser = fn(string $line): array => [TestConstants::FIELD_MESSAGE => $line, 'context' => []];
$results = iterator_to_array($processor->processFile($tempFile, $lineParser));
@@ -144,7 +144,7 @@ final class StreamingProcessorTest extends TestCase
$this->expectException(StreamingOperationFailedException::class);
$this->expectExceptionMessage('Cannot open input file for streaming:');
iterator_to_array($processor->processFile('/nonexistent/path/file.log', fn(string $l): array => ['message' => $l, 'context' => []]));
iterator_to_array($processor->processFile('/nonexistent/path/file.log', fn(string $l): array => [TestConstants::FIELD_MESSAGE => $l, 'context' => []]));
}
public function testProcessToFile(): void
@@ -152,15 +152,15 @@ final class StreamingProcessorTest extends TestCase
$processor = new StreamingProcessor($this->createOrchestrator(), 10);
$records = [
['message' => 'test line 1', 'context' => []],
['message' => 'test line 2', 'context' => []],
[TestConstants::FIELD_MESSAGE => 'test line 1', 'context' => []],
[TestConstants::FIELD_MESSAGE => 'test line 2', 'context' => []],
];
$outputFile = tempnam(sys_get_temp_dir(), 'gdpr_output_');
$this->assertIsString($outputFile, 'Failed to create temp file');
try {
$formatter = fn(array $record): string => $record['message'];
$formatter = fn(array $record): string => $record[TestConstants::FIELD_MESSAGE];
$count = $processor->processToFile($records, $outputFile, $formatter);
$this->assertSame(2, $count);
@@ -188,15 +188,33 @@ final class StreamingProcessorTest extends TestCase
$processor = new StreamingProcessor($this->createOrchestrator(), 10);
$records = [
['message' => 'test masked', 'context' => []],
['message' => 'no sensitive data', 'context' => []],
['message' => 'another test here', 'context' => []],
[TestConstants::FIELD_MESSAGE => 'test masked', 'context' => []],
[TestConstants::FIELD_MESSAGE => 'no sensitive data', 'context' => []],
[TestConstants::FIELD_MESSAGE => 'another test here', 'context' => []],
];
$stats = $processor->getStatistics($records);
$this->assertSame(3, $stats['processed']);
$this->assertGreaterThan(0, $stats['masked']); // At least some should be masked
$this->assertSame(2, $stats[TestConstants::DATA_MASKED]);
}
public function testGetStatisticsDoesNotTriggerAuditLogger(): void
{
$auditCalled = false;
$auditLogger = function () use (&$auditCalled): void {
$auditCalled = true;
};
$processor = new StreamingProcessor($this->createOrchestrator(), 10, $auditLogger);
$records = [
[TestConstants::FIELD_MESSAGE => TestConstants::DATA_TEST_DATA, 'context' => []],
];
$processor->getStatistics($records);
$this->assertFalse($auditCalled);
}
public function testSetAuditLogger(): void
@@ -209,7 +227,7 @@ final class StreamingProcessorTest extends TestCase
$processor = new StreamingProcessor($this->createOrchestrator(), 1);
$processor->setAuditLogger($auditLogger);
$records = [['message' => 'test', 'context' => []]];
$records = [[TestConstants::FIELD_MESSAGE => 'test', 'context' => []]];
iterator_to_array($processor->processStream($records));
$this->assertNotEmpty($logs);
@@ -235,7 +253,7 @@ final class StreamingProcessorTest extends TestCase
$records = [];
for ($i = 1; $i <= 500; $i++) {
$records[] = ['message' => "test record {$i}", 'context' => []];
$records[] = [TestConstants::FIELD_MESSAGE => "test record {$i}", 'context' => []];
}
$count = 0;

View File

@@ -145,6 +145,8 @@ final class TestConstants
public const FIELD_USER_NAME = 'user.name';
public const FIELD_USER_PUBLIC = 'user.public';
public const FIELD_USER_PASSWORD = 'user.password';
public const FIELD_USER_SSN = 'user.ssn';
public const FIELD_USER_DATA = 'user.data';
public const FIELD_SYSTEM_LOG = 'system.log';
// Path Patterns
@@ -168,20 +170,32 @@ final class TestConstants
// Mask placeholders used in tests (bracketed format)
public const MASK_REDACTED_BRACKETS = '[REDACTED]';
public const MASK_MASKED_BRACKETS = '[MASKED]';
public const MASK_SECRET_BRACKETS = '[SECRET]';
public const MASK_SSN_BRACKETS = '[SSN]';
public const MASK_EMAIL_BRACKETS = '[EMAIL]';
public const MASK_DIGITS_BRACKETS = '[DIGITS]';
public const MASK_INT_BRACKETS = '[INT]';
public const MASK_ALWAYS_THIS = '[ALWAYS_THIS]';
public const MASK_REDACTED_PLAIN = 'REDACTED';
// Test values
public const VALUE_TEST = 'test value';
public const VALUE_SUFFIX = ' value';
// Expected output strings
public const EXPECTED_SSN_MASKED = 'SSN: [SSN]';
// Mask placeholders (bracketed format, additional)
public const MASK_CARD_BRACKETS = '[CARD]';
// Additional pattern constants
public const PATTERN_EMAIL_SIMPLE = '/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/';
public const PATTERN_VALID_SIMPLE = '/^test$/';
public const PATTERN_INVALID_UNCLOSED = '/unclosed';
public const PATTERN_REDOS_VULNERABLE = '/^(a+)+$/';
public const PATTERN_REDOS_NESTED_STAR = '/^(a*)*$/';
public const PATTERN_SAFE = '/[a-z]+/';
public const PATTERN_CREDIT_CARD = '/\b\d{4}[\s-]?\d{4}[\s-]?\d{4}[\s-]?\d{4}\b/';
/**
* Prevent instantiation.