mirror of
https://github.com/ivuorinen/monolog-gdpr-filter.git
synced 2026-03-17 22:02:59 +00:00
Compare commits
30 Commits
d4fde461fd
...
renovate/s
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9ae4901a14 | ||
|
|
347faa80d1 | ||
|
|
4d9f925e89 | ||
|
|
d43de372e3 | ||
|
|
16a986001c | ||
|
|
8425411b6c | ||
|
|
57acc7847f | ||
| d1cbf50c5e | |||
|
|
e26312a6ee | ||
| b0925ce489 | |||
| e58397a75d | |||
| f6b0f864b4 | |||
|
|
0fd7cd099f | ||
| 38946574a4 | |||
|
|
1be44fff9d | ||
|
|
3be9c07d6c | ||
|
|
8ec91aad35 | ||
|
|
5eb01578d2 | ||
|
|
110598e921 | ||
|
|
9af85cb9b1 | ||
|
|
1a60d2b573 | ||
|
|
97ac6b1eae | ||
| 47564c5cd6 | |||
|
|
3d3448dcf0 | ||
|
|
f16eb2a095 | ||
|
|
451726a365 | ||
|
|
966618ec5a | ||
|
|
c3f5ddcc45 | ||
|
|
e499663b5d | ||
|
|
c89bc1ae72 |
4
.github/copilot-instructions.md
vendored
4
.github/copilot-instructions.md
vendored
@@ -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.
|
||||
|
||||
36
.github/workflows/ci.yml
vendored
36
.github/workflows/ci.yml
vendored
@@ -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
|
||||
|
||||
30
.github/workflows/codeql.yml
vendored
30
.github/workflows/codeql.yml
vendored
@@ -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}}"
|
||||
|
||||
4
.github/workflows/phpcs.yaml
vendored
4
.github/workflows/phpcs.yaml
vendored
@@ -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)
|
||||
|
||||
6
.github/workflows/pr-lint.yml
vendored
6
.github/workflows/pr-lint.yml
vendored
@@ -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
|
||||
|
||||
47
.github/workflows/release.yml
vendored
47
.github/workflows/release.yml
vendored
@@ -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
|
||||
|
||||
2
.github/workflows/stale.yml
vendored
2
.github/workflows/stale.yml
vendored
@@ -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
|
||||
|
||||
4
.github/workflows/sync-labels.yml
vendored
4
.github/workflows/sync-labels.yml
vendored
@@ -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
|
||||
|
||||
14
.github/workflows/test-coverage.yaml
vendored
14
.github/workflows/test-coverage.yaml
vendored
@@ -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
1
.php-version
Normal file
@@ -0,0 +1 @@
|
||||
8.4
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -24,7 +24,7 @@ Please be respectful in all interactions.
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- PHP 8.2 or higher
|
||||
- PHP 8.4 or higher
|
||||
- Composer
|
||||
- Git
|
||||
|
||||
|
||||
@@ -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) |
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
2
TODO.md
2
TODO.md
@@ -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)
|
||||
|
||||
|
||||
@@ -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
1383
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -91,7 +91,7 @@ parameters:
|
||||
- '#Constant Tests\\.*::.* is unused#'
|
||||
|
||||
# PHP version for analysis
|
||||
phpVersion: 80200
|
||||
phpVersion: 80400
|
||||
|
||||
# Stub files for missing functions/classes
|
||||
stubFiles: []
|
||||
|
||||
@@ -21,8 +21,4 @@
|
||||
</source>
|
||||
|
||||
<coverage/>
|
||||
|
||||
<php>
|
||||
<ini name="xdebug.mode" value="coverage"/>
|
||||
</php>
|
||||
</phpunit>
|
||||
|
||||
@@ -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) -->
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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']++;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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',
|
||||
],
|
||||
|
||||
@@ -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']);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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',
|
||||
]);
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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']);
|
||||
|
||||
@@ -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,
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
@@ -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,
|
||||
],
|
||||
];
|
||||
|
||||
|
||||
@@ -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]];
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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'
|
||||
]);
|
||||
|
||||
@@ -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' => '[]',
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -286,7 +286,7 @@ final class RetryStrategyTest extends TestCase
|
||||
$auditLogs[] = [
|
||||
'path' => $path,
|
||||
'original' => $original,
|
||||
'masked' => $masked
|
||||
TestConstants::DATA_MASKED => $masked
|
||||
];
|
||||
};
|
||||
|
||||
|
||||
@@ -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']);
|
||||
|
||||
@@ -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"]);
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ final class ConditionalMaskingStrategyComprehensiveTest extends TestCase
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return 'Test Strategy';
|
||||
return TestConstants::STRATEGY_TEST;
|
||||
}
|
||||
|
||||
public function validate(): bool
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user