1 Commits

Author SHA1 Message Date
renovate[bot]
a5960428bb chore(deps): update ivuorinen/actions action (v2026.01.13 → v2026.01.21)
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-27 11:30:02 +00:00
77 changed files with 1288 additions and 1241 deletions

View File

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

View File

@@ -8,18 +8,14 @@ on:
pull_request:
branches: [main, develop]
permissions: {}
jobs:
test:
runs-on: ubuntu-latest
permissions:
contents: read
strategy:
fail-fast: false
matrix:
php-version: ["8.4", "8.5"]
php-version: ["8.2", "8.3", "8.4"]
name: PHP ${{ matrix.php-version }}
@@ -33,21 +29,21 @@ jobs:
php-version: ${{ matrix.php-version }}
extensions: mbstring, xml, ctype, iconv, intl, json
tools: composer:v2
coverage: pcov
coverage: xdebug
- 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@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1
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
run: composer install --prefer-dist --no-progress --no-suggest
- name: Run PHPUnit tests
run: composer test
@@ -67,8 +63,6 @@ jobs:
coverage:
runs-on: ubuntu-latest
name: Coverage
permissions:
contents: read
steps:
- name: Checkout code
@@ -77,16 +71,16 @@ jobs:
- name: Setup PHP
uses: shivammathur/setup-php@44454db4f0199b8b9685a5d763dc37cbf79108e1 # 2.36.0
with:
php-version-file: '.php-version'
php-version: "8.2"
extensions: mbstring, xml, ctype, iconv, intl, json
tools: composer:v2
coverage: pcov
coverage: xdebug
- name: Install dependencies
run: composer install --prefer-dist --no-progress
run: composer install --prefer-dist --no-progress --no-suggest
- name: Run tests with coverage
run: composer test:ci
run: composer test:coverage
- name: Upload coverage reports to Codecov
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
@@ -98,8 +92,6 @@ jobs:
security:
runs-on: ubuntu-latest
name: Security Analysis
permissions:
contents: read
steps:
- name: Checkout code
@@ -108,12 +100,12 @@ jobs:
- name: Setup PHP
uses: shivammathur/setup-php@44454db4f0199b8b9685a5d763dc37cbf79108e1 # 2.36.0
with:
php-version-file: '.php-version'
php-version: "8.2"
extensions: mbstring, xml, ctype, iconv, intl, json
tools: composer:v2
- name: Install dependencies
run: composer install --prefer-dist --no-progress
run: composer install --prefer-dist --no-progress --no-suggest
- name: Run security audit
run: composer audit

View File

@@ -8,27 +8,39 @@ on:
pull_request:
branches: ["main"]
schedule:
- cron: "30 1 * * 0"
- cron: "30 1 * * 0" # Run at 1:30 AM UTC every Sunday
merge_group:
permissions: {}
permissions:
actions: read
contents: read
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"]
language: ["actions"] # Add languages used in your actions
steps:
- name: CodeQL Analysis
uses: ivuorinen/actions/codeql-analysis@7f6a23b59316795c4b3cb3b3b28dd53e53655a33 # v2026.03.11
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Initialize CodeQL
uses: github/codeql-action/init@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v4.31.9
with:
language: ${{ matrix.language }}
languages: ${{ matrix.language }}
queries: security-and-quality
- name: Autobuild
uses: github/codeql-action/autobuild@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v4.31.9
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v4.31.9
with:
category: "/language:${{matrix.language}}"

View File

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

View File

@@ -4,9 +4,9 @@ name: Lint Code Base
on:
push:
branches: [main]
branches: [master, main]
pull_request:
branches: [main]
branches: [master, 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@7f6a23b59316795c4b3cb3b3b28dd53e53655a33 # v2026.03.11
uses: ivuorinen/actions/pr-lint@f98ae7cd7d0feb1f9d6b01de0addbb11414cfc73 # v2026.01.21

View File

@@ -24,12 +24,12 @@ jobs:
- name: Setup PHP
uses: shivammathur/setup-php@44454db4f0199b8b9685a5d763dc37cbf79108e1 # 2.36.0
with:
php-version-file: '.php-version'
php-version: "8.2"
extensions: mbstring, xml, ctype, iconv, intl, json
tools: composer:v2
- name: Install dependencies
run: composer install --prefer-dist --no-progress --optimize-autoloader
run: composer install --prefer-dist --no-progress --no-suggest --no-dev --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,28 +49,39 @@ 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"
cat /tmp/changelog.txt
echo "EOF"
} >> "$GITHUB_OUTPUT"
echo "content<<EOF" >> $GITHUB_OUTPUT
cat /tmp/changelog.txt >> $GITHUB_OUTPUT
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: Create Release
uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2.5.0
- name: Upload release asset
uses: actions/upload-release-asset@e8f9f06c4b078e705bd2ea027f0926603fc9b4d5 # v1.0.2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
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
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

View File

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

View File

@@ -39,4 +39,4 @@ jobs:
with:
token: ${{ secrets.GITHUB_TOKEN }}
- name: ⤵️ Sync Latest Labels Definitions
uses: ivuorinen/actions/sync-labels@7f6a23b59316795c4b3cb3b3b28dd53e53655a33 # v2026.03.11
uses: ivuorinen/actions/sync-labels@f98ae7cd7d0feb1f9d6b01de0addbb11414cfc73 # v2026.01.21

View File

@@ -8,7 +8,7 @@ on:
push:
branches: [main]
permissions: {}
permissions: read-all
jobs:
test:
@@ -25,7 +25,7 @@ jobs:
uses: shivammathur/setup-php@44454db4f0199b8b9685a5d763dc37cbf79108e1 # 2.36.0
with:
coverage: pcov
php-version-file: '.php-version'
php-version: "8.2"
- 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@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.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@70d2764d1a7d5d9560b100cbea0077fc8f633987 # v3.0.2
uses: marocchino/sticky-pull-request-comment@773744901bac0e8cbb5a0dc842800d45e9b2b405 # v2.9.4
if: github.event_name == 'pull_request'
with:
recreate: true

View File

@@ -1 +0,0 @@
8.4

View File

@@ -22,7 +22,7 @@ repos:
args: [--autofix, --no-sort-keys]
- repo: https://github.com/igorshubovych/markdownlint-cli
rev: v0.48.0
rev: v0.47.0
hooks:
- id: markdownlint
args: [-c, .markdownlint.json, --fix]
@@ -44,13 +44,18 @@ repos:
args: ["--severity=warning"]
- repo: https://github.com/rhysd/actionlint
rev: v1.7.11
rev: v1.7.10
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.508"
rev: "3.2.497"
hooks:
- id: checkov
args:

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -20,9 +20,9 @@
"@lint:tool:md:fix",
"@lint:tool:ec:fix"
],
"test": "./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",
"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",
"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": "npx -y markdownlint-cli -f '**/*.md'",
"lint:tool:md": "npx -y markdownlint-cli '**/*.md'"
"lint:tool:md:fix": "markdownlint -f '**/*.md'",
"lint:tool:md": "markdownlint '**/*.md'"
},
"require": {
"php": "^8.4",
"php": "^8.2",
"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": "^13",
"phpunit/phpunit": "^11",
"psalm/plugin-phpunit": "^0.19.5",
"rector/rector": "^2.1",
"squizlabs/php_codesniffer": "^4.0",

1497
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,4 @@
FROM php:8.5-cli-alpine
FROM php:8.2-cli-alpine
# Install system dependencies
RUN apk add --no-cache \
@@ -15,10 +15,12 @@ RUN apk add --no-cache \
# Install Composer
COPY --from=composer:2 /usr/bin/composer /usr/bin/composer
# Install PCOV for code coverage
# Install and configure Xdebug for code coverage
RUN apk add --no-cache $PHPIZE_DEPS \
&& pecl install pcov \
&& docker-php-ext-enable pcov
&& 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
# Set working directory
WORKDIR /app

View File

@@ -11,13 +11,14 @@ 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.5-cli-alpine
image: php:8.3-cli-alpine
volumes:
- ..:/app
working_dir: /app

View File

@@ -26,7 +26,7 @@ docker compose exec php composer lint
### docker/Dockerfile
```dockerfile
FROM php:8.4-cli-alpine
FROM php:8.2-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.5 for testing compatibility
# Optional: PHP 8.3 for testing compatibility
php83:
image: php:8.5-cli-alpine
image: php:8.3-cli-alpine
volumes:
- ..:/app
working_dir: /app
@@ -253,7 +253,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
php: ['8.4', '8.5']
php: ['8.2', '8.3']
steps:
- uses: actions/checkout@v4

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -35,7 +35,7 @@ class AdvancedRegexMaskProcessorTest extends TestCase
];
$fieldPaths = [
TestConstants::FIELD_USER_SSN => "[GDPR]",
"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[TestConstants::FIELD_MESSAGE]);
$this->assertSame("Card: " . MaskConstants::MASK_CC, $result["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[TestConstants::FIELD_MESSAGE]);
$this->assertSame("Email: " . MaskConstants::MASK_EMAIL, $result["message"]);
}
public function testContextFieldPathReplacements(): void

View File

@@ -19,11 +19,11 @@ final class KAnonymizerTest extends TestCase
$anonymizer = new KAnonymizer();
$anonymizer->registerAgeStrategy('age');
$record = ['name' => TestConstants::NAME_FIRST, 'age' => 25];
$record = ['name' => 'John', 'age' => 25];
$result = $anonymizer->anonymize($record);
$this->assertSame(TestConstants::AGE_RANGE_20_29, $result['age']);
$this->assertSame(TestConstants::NAME_FIRST, $result['name']);
$this->assertSame('John', $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' => TestConstants::DATA_NUMBER_STRING];
$record = ['zip_code' => '12345'];
$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(TestConstants::CONTEXT_EMAIL, fn(mixed $v): string => explode('@', (string) $v)[1] ?? 'unknown');
$anonymizer->registerCustomStrategy('email', fn(mixed $v): string => explode('@', (string) $v)[1] ?? 'unknown');
$record = [TestConstants::CONTEXT_EMAIL => TestConstants::EMAIL_JOHN];
$record = ['email' => 'john@example.com'];
$result = $anonymizer->anonymize($record);
$this->assertSame(TestConstants::DOMAIN, $result[TestConstants::CONTEXT_EMAIL]);
$this->assertSame('example.com', $result['email']);
}
public function testRegisterStrategy(): void
{
$strategy = new GeneralizationStrategy(fn(mixed $v): string => TestConstants::DATA_MASKED, 'test');
$strategy = new GeneralizationStrategy(fn(mixed $v): string => '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(TestConstants::DATA_MASKED, $result['field']);
$this->assertSame('masked', $result['field']);
}
public function testAnonymizeIgnoresMissingFields(): void
@@ -141,10 +141,10 @@ final class KAnonymizerTest extends TestCase
$anonymizer = new KAnonymizer();
$anonymizer->registerAgeStrategy('age');
$record = ['name' => TestConstants::NAME_FIRST];
$record = ['name' => 'John'];
$result = $anonymizer->anonymize($record);
$this->assertSame(['name' => TestConstants::NAME_FIRST], $result);
$this->assertSame(['name' => 'John'], $result);
}
public function testAnonymizeBatch(): void
@@ -153,7 +153,7 @@ final class KAnonymizerTest extends TestCase
$anonymizer->registerAgeStrategy('age');
$records = [
['name' => TestConstants::NAME_FIRST, 'age' => 25],
['name' => 'John', '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' => TestConstants::DATA_NUMBER_STRING, 'date' => '2024-06-15', 'name' => TestConstants::NAME_FIRST];
$record = ['age' => 28, 'zip' => '12345', 'date' => '2024-06-15', 'name' => 'John'];
$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(TestConstants::NAME_FIRST, $result['name']);
$this->assertSame('John', $result['name']);
}
public function testFluentInterface(): void

View File

@@ -8,7 +8,6 @@ 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.
@@ -30,11 +29,11 @@ final class ArrayAccessorFactoryTest extends TestCase
{
$factory = ArrayAccessorFactory::default();
$accessor = $factory->create([
'user' => [TestConstants::CONTEXT_EMAIL => TestConstants::EMAIL_TEST],
'user' => ['email' => 'test@example.com'],
]);
$this->assertTrue($accessor->has(TestConstants::FIELD_USER_EMAIL));
$this->assertSame(TestConstants::EMAIL_TEST, $accessor->get(TestConstants::FIELD_USER_EMAIL));
$this->assertTrue($accessor->has('user.email'));
$this->assertSame('test@example.com', $accessor->get('user.email'));
}
public function testWithClassFactoryMethod(): void

View File

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

View File

@@ -111,10 +111,10 @@ final class ErrorContextTest extends TestCase
public function testSanitizesConnectionStrings(): void
{
$message = 'Failed: redis://admin:' . TestConstants::CONTEXT_PASSWORD . '@localhost:6379';
$message = 'Failed: redis://admin:password@localhost:6379';
$context = ErrorContext::create('ConnError', $message);
$this->assertStringNotContainsString(TestConstants::CONTEXT_PASSWORD, $context->message);
$this->assertStringNotContainsString('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: TestConstants::MESSAGE_DEFAULT,
message: 'Test message',
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(TestConstants::FIELD_MESSAGE, $array);
$this->assertArrayHasKey('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(TestConstants::MESSAGE_DEFAULT, $array[TestConstants::FIELD_MESSAGE]);
$this->assertSame('Test message', $array['message']);
$this->assertSame(100, $array['code']);
}

View File

@@ -43,7 +43,7 @@ final class StructuredAuditLoggerTest extends TestCase
$this->logs[] = [
'path' => $path,
'original' => $original,
TestConstants::DATA_MASKED => $masked
'masked' => $masked
];
};
}
@@ -52,12 +52,12 @@ final class StructuredAuditLoggerTest extends TestCase
{
$logger = new StructuredAuditLogger($this->createBaseLogger());
$logger->log(TestConstants::FIELD_USER_EMAIL, TestConstants::EMAIL_JOHN, TestConstants::MASK_MASKED_BRACKETS);
$logger->log('user.email', TestConstants::EMAIL_JOHN, TestConstants::MASK_MASKED_BRACKETS);
$this->assertCount(1, $this->logs);
$this->assertSame(TestConstants::FIELD_USER_EMAIL, $this->logs[0]['path']);
$this->assertSame('user.email', $this->logs[0]['path']);
$this->assertSame(TestConstants::EMAIL_JOHN, $this->logs[0]['original']);
$this->assertSame(TestConstants::MASK_MASKED_BRACKETS, $this->logs[0][TestConstants::DATA_MASKED]);
$this->assertSame(TestConstants::MASK_MASKED_BRACKETS, $this->logs[0]['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(TestConstants::FIELD_USER_EMAIL, TestConstants::EMAIL_JOHN, TestConstants::MASK_MASKED_BRACKETS, $context);
$logger->log('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(
TestConstants::FIELD_USER_SSN,
TestConstants::SSN_US,
TestConstants::MASK_SSN_BRACKETS,
'user.ssn',
'123-45-6789',
'[SSN]',
AuditContext::OP_REGEX,
10.5
);
$this->assertCount(1, $this->logs);
$this->assertSame(TestConstants::FIELD_USER_SSN, $this->logs[0]['path']);
$this->assertSame('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(
TestConstants::FIELD_USER_DATA,
'user.data',
'sensitive value',
AuditContext::OP_REGEX,
$error
);
$this->assertCount(1, $this->logs);
$this->assertSame('[MASKING_FAILED]', $this->logs[0][TestConstants::DATA_MASKED]);
$this->assertSame('[MASKING_FAILED]', $this->logs[0]['masked']);
}
public function testLogRecovery(): void
@@ -107,7 +107,7 @@ final class StructuredAuditLoggerTest extends TestCase
$logger = new StructuredAuditLogger($this->createBaseLogger());
$logger->logRecovery(
TestConstants::FIELD_USER_EMAIL,
'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][TestConstants::DATA_MASKED]);
$this->assertSame(TestConstants::NAME_FULL, $this->logs[0]['masked']);
}
public function testWrapStaticFactory(): void
{
$logger = StructuredAuditLogger::wrap($this->createBaseLogger());
$logger->log('test.path', 'original', TestConstants::DATA_MASKED);
$logger->log('test.path', 'original', 'masked');
$this->assertCount(1, $this->logs);
}
@@ -152,7 +152,7 @@ final class StructuredAuditLoggerTest extends TestCase
);
$logger = new StructuredAuditLogger($rateLimited);
$logger->log(TestConstants::FIELD_USER_EMAIL, TestConstants::EMAIL_JOHN, TestConstants::MASK_MASKED_BRACKETS);
$logger->log('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', TestConstants::DATA_MASKED);
$wrapped('test.path', 'original', 'masked');
$this->assertCount(1, $this->logs);
}
@@ -188,7 +188,7 @@ final class StructuredAuditLoggerTest extends TestCase
includeTimestamp: false
);
$logger->log('test', 'original', TestConstants::DATA_MASKED);
$logger->log('test', 'original', 'masked');
$this->assertCount(1, $this->logs);
}
@@ -200,7 +200,7 @@ final class StructuredAuditLoggerTest extends TestCase
includeDuration: false
);
$logger->log('test', 'original', TestConstants::DATA_MASKED);
$logger->log('test', 'original', 'masked');
$this->assertCount(1, $this->logs);
}

View File

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

View File

@@ -58,7 +58,7 @@ final class GdprProcessorBuilderTest extends TestCase
{
$patterns = [
TestConstants::PATTERN_DIGITS => TestConstants::MASK_DIGITS_BRACKETS,
TestConstants::PATTERN_TEST => TestConstants::REPLACEMENT_TEST,
TestConstants::PATTERN_TEST => '[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 => TestConstants::REPLACEMENT_TEST]);
->setPatterns([TestConstants::PATTERN_TEST => '[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(TestConstants::MASK_EMAIL_BRACKETS));
->addFieldPath(TestConstants::CONTEXT_EMAIL, FieldMaskConfig::replace('[EMAIL]'));
$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(TestConstants::MASK_EMAIL_BRACKETS),
TestConstants::CONTEXT_EMAIL => FieldMaskConfig::replace('[EMAIL]'),
TestConstants::CONTEXT_PASSWORD => FieldMaskConfig::remove(),
];
@@ -128,7 +128,7 @@ final class GdprProcessorBuilderTest extends TestCase
};
$processor = GdprProcessorBuilder::create()
->addFieldPath('field', FieldMaskConfig::replace(MaskConstants::MASK_BRACKETS))
->addFieldPath('field', FieldMaskConfig::replace('[MASKED]'))
->withAuditLogger($auditLogger)
->build();
@@ -229,7 +229,7 @@ final class GdprProcessorBuilderTest extends TestCase
public function getPatterns(): array
{
return [TestConstants::PATTERN_SECRET => TestConstants::MASK_SECRET_BRACKETS];
return ['/secret/' => '[SECRET]'];
}
};
@@ -297,7 +297,7 @@ final class GdprProcessorBuilderTest extends TestCase
public function getPatterns(): array
{
return [TestConstants::PATTERN_SECRET => TestConstants::MASK_SECRET_BRACKETS];
return ['/secret/' => '[SECRET]'];
}
};

View File

@@ -33,7 +33,7 @@ final class PluginAwareProcessorTest extends TestCase
};
$processor = GdprProcessorBuilder::create()
->addPattern('/TEST/', MaskConstants::MASK_BRACKETS)
->addPattern('/TEST/', '[MASKED]')
->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(TestConstants::MASK_MASKED_BRACKETS, $result->message);
$this->assertStringContainsString('[MASKED]', $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(TestConstants::MESSAGE_TEST_LOWERCASE));
$this->assertSame(MaskConstants::MASK_GENERIC . ' message', $processor->regExpMessage('test message'));
}
public function testRecursiveMaskDelegates(): void
@@ -282,9 +282,9 @@ final class PluginAwareProcessorTest extends TestCase
$this->assertInstanceOf(PluginAwareProcessor::class, $processor);
$result = $processor->recursiveMask(['key' => TestConstants::VALUE_TEST]);
$result = $processor->recursiveMask(['key' => 'test value']);
$this->assertSame(MaskConstants::MASK_GENERIC . TestConstants::VALUE_SUFFIX, $result['key']);
$this->assertSame(MaskConstants::MASK_GENERIC . ' value', $result['key']);
}
public function testSetAuditLoggerDelegates(): void

View File

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

View File

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

View File

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

View File

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

View File

@@ -7,7 +7,6 @@ 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
@@ -78,7 +77,7 @@ final class MaskingOperationFailedExceptionTest extends TestCase
$previous = new \RuntimeException('Inner error');
$exception = MaskingOperationFailedException::fieldPathMaskingFailed(
TestConstants::FIELD_USER_DATA,
'user.data',
['complex' => 'value'],
'Failed',
$previous

View File

@@ -9,7 +9,6 @@ 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
@@ -67,12 +66,12 @@ final class AuditLoggerFactoryTest extends TestCase
$storage = [];
$logger = $factory->createArrayLogger($storage);
$logger('test.path', 'original', TestConstants::DATA_MASKED);
$logger('test.path', 'original', 'masked');
$this->assertCount(1, $storage);
$this->assertSame('test.path', $storage[0]['path']);
$this->assertSame('original', $storage[0]['original']);
$this->assertSame(TestConstants::DATA_MASKED, $storage[0][TestConstants::DATA_MASKED]);
$this->assertSame('masked', $storage[0]['masked']);
$this->assertArrayHasKey('timestamp', $storage[0]);
}
@@ -91,7 +90,7 @@ final class AuditLoggerFactoryTest extends TestCase
$logger = $factory->createNullLogger();
// Should not throw
$logger('path', 'original', TestConstants::DATA_MASKED);
$logger('path', 'original', 'masked');
$this->assertTrue(true);
}
@@ -110,11 +109,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, TestConstants::DATA_MASKED => $masked];
$calls[] = ['path' => $path, 'original' => $original, 'masked' => $masked];
};
$logger = $factory->createCallbackLogger($callback);
$logger('test.path', 'original', TestConstants::DATA_MASKED);
$logger('test.path', 'original', 'masked');
$this->assertCount(1, $calls);
$this->assertSame('test.path', $calls[0]['path']);

View File

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

View File

@@ -59,10 +59,10 @@ final class GdprProcessorEdgeCasesTest extends TestCase
);
// Call maskMessage directly
$result = $processor->maskMessage(TestConstants::VALUE_TEST);
$result = $processor->maskMessage('test value');
// Should work normally
$this->assertSame(Mask::MASK_MASKED . TestConstants::VALUE_SUFFIX, $result);
$this->assertSame(Mask::MASK_MASKED . ' value', $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' => TestConstants::MESSAGE_SECRET_DATA,
'level2' => 'secret data',
],
];

View File

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

View File

@@ -58,7 +58,7 @@ class GdprProcessorTest extends TestCase
{
$patterns = DefaultPatterns::get();
$fieldPaths = [
TestConstants::FIELD_USER_SSN => FieldMaskConfig::remove(),
'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',
TestConstants::PATTERN_SAFE => 'LETTERS'
'/[a-z]+/' => 'LETTERS'
];
$processor = $this->createProcessor($validPatterns);

View File

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

View File

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

View File

@@ -84,7 +84,7 @@ final class InputValidatorTest extends TestCase
{
InputValidator::validatePatterns([
TestConstants::PATTERN_SSN_FORMAT => MaskConstants::MASK_SSN_PATTERN,
TestConstants::PATTERN_SAFE => TestConstants::MASK_REDACTED_PLAIN,
'/[a-z]+/' => 'REDACTED',
]);
$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(),
TestConstants::FIELD_USER_SSN => $ssnConfig,
'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' => TestConstants::MASK_REDACTED_PLAIN,
'string' => 'REDACTED',
'boolean' => MaskConstants::MASK_GENERIC,
'NULL' => 'null',
'array' => '[]',

View File

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

View File

@@ -25,7 +25,7 @@ final class MaskingOrchestratorTest extends TestCase
$result = $orchestrator->process('This is a test message', []);
$this->assertSame('This is a ' . MaskConstants::MASK_GENERIC . ' message', $result[TestConstants::FIELD_MESSAGE]);
$this->assertSame('This is a ' . MaskConstants::MASK_GENERIC . ' message', $result['message']);
$this->assertSame([], $result['context']);
}
@@ -35,9 +35,9 @@ final class MaskingOrchestratorTest extends TestCase
[TestConstants::PATTERN_TEST => MaskConstants::MASK_GENERIC]
);
$result = $orchestrator->process(TestConstants::FIELD_MESSAGE, ['key' => TestConstants::VALUE_TEST]);
$result = $orchestrator->process('message', ['key' => TestConstants::VALUE_TEST]);
$this->assertSame(TestConstants::FIELD_MESSAGE, $result[TestConstants::FIELD_MESSAGE]);
$this->assertSame('message', $result['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(TestConstants::FIELD_MESSAGE, [TestConstants::CONTEXT_EMAIL => TestConstants::EMAIL_TEST]);
$result = $orchestrator->process('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(TestConstants::FIELD_MESSAGE, ['name' => 'john']);
$result = $orchestrator->process('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 => TestConstants::MASK_SSN_BRACKETS]
[TestConstants::PATTERN_SSN_FORMAT => '[SSN]']
);
$result = $orchestrator->regExpMessage('SSN: 123-45-6789');
$this->assertSame(TestConstants::EXPECTED_SSN_MASKED, $result);
$this->assertSame('SSN: [SSN]', $result);
}
public function testRegExpMessagePreservesEmptyString(): void
@@ -115,7 +115,7 @@ final class MaskingOrchestratorTest extends TestCase
[TestConstants::PATTERN_TEST => MaskConstants::MASK_GENERIC]
);
$result = $orchestrator->recursiveMask(TestConstants::MESSAGE_TEST_STRING);
$result = $orchestrator->recursiveMask('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 => TestConstants::MASK_DIGITS_BRACKETS],
[TestConstants::PATTERN_DIGITS => '[DIGITS]'],
[],
[],
null,
@@ -178,7 +178,7 @@ final class MaskingOrchestratorTest extends TestCase
$orchestrator = new MaskingOrchestrator(
[],
['field' => FieldMaskConfig::replace(MaskConstants::MASK_BRACKETS)]
['field' => FieldMaskConfig::replace('[MASKED]')]
);
$orchestrator->setAuditLogger($auditLogger);
@@ -218,11 +218,11 @@ final class MaskingOrchestratorTest extends TestCase
[
TestConstants::CONTEXT_EMAIL => TestConstants::EMAIL_TEST,
'name' => 'john',
TestConstants::FIELD_MESSAGE => 'test'
'message' => 'test'
]
);
$this->assertSame('Hello ' . MaskConstants::MASK_GENERIC, $result[TestConstants::FIELD_MESSAGE]);
$this->assertSame('Hello ' . MaskConstants::MASK_GENERIC, $result['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' => TestConstants::MASK_INT_BRACKETS]
['integer' => '[INT]']
);
$result = $orchestrator->processContext(['count' => 42]);
$this->assertSame(TestConstants::MASK_INT_BRACKETS, $result['count']);
$this->assertSame('[INT]', $result['count']);
}
public function testProcessContextWithRemoveConfig(): void

View File

@@ -8,7 +8,6 @@ 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
@@ -93,7 +92,7 @@ final class AbstractMaskingPluginTest extends TestCase
}
};
$message = TestConstants::MESSAGE_TEST_LOWERCASE;
$message = 'test message';
$result = $plugin->preProcessMessage($message);
$this->assertSame($message, $result);
@@ -108,7 +107,7 @@ final class AbstractMaskingPluginTest extends TestCase
}
};
$message = TestConstants::MESSAGE_TEST_LOWERCASE;
$message = 'test message';
$result = $plugin->postProcessMessage($message);
$this->assertSame($message, $result);
@@ -186,13 +185,13 @@ final class AbstractMaskingPluginTest extends TestCase
public function getPatterns(): array
{
return [TestConstants::PATTERN_SECRET => TestConstants::MASK_REDACTED_BRACKETS];
return ['/secret/' => '[REDACTED]'];
}
};
$patterns = $plugin->getPatterns();
$this->assertArrayHasKey(TestConstants::PATTERN_SECRET, $patterns);
$this->assertSame(TestConstants::MASK_REDACTED_BRACKETS, $patterns[TestConstants::PATTERN_SECRET]);
$this->assertArrayHasKey('/secret/', $patterns);
$this->assertSame('[REDACTED]', $patterns['/secret/']);
}
}

View File

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

View File

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

View File

@@ -115,7 +115,7 @@ final class RecursiveProcessorTest extends TestCase
$dataTypeMasker = new DataTypeMasker([]);
$processor = new RecursiveProcessor($regexProcessor, $dataTypeMasker, null, 10);
$data = ['field1' => TestConstants::MESSAGE_SECRET_DATA, 'field2' => TestConstants::DATA_PUBLIC];
$data = ['field1' => '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(TestConstants::MESSAGE_TEST_STRING, 0);
$result = $processor->processValue('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, TestConstants::MASK_REDACTED_BRACKETS, $val);
$regexProcessor = fn(string $val): string => str_replace(TestConstants::CONTEXT_PASSWORD, '[REDACTED]', $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' => TestConstants::MESSAGE_SECRET_DATA], 0);
$result = $processor->processArrayValue(['key' => 'secret data'], 0);
$this->assertIsArray($result);
$this->assertSame('*** data', $result['key']);

View File

@@ -40,7 +40,7 @@ class RegexMaskProcessorTest extends TestCase
"/\b\d{6}[-+A]?\d{3}[A-Z]\b/u" => Mask::MASK_MASKED,
];
$fieldPaths = [
TestConstants::FIELD_USER_SSN => self::GDPR_REPLACEMENT,
"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 = [TestConstants::FIELD_USER_SSN => FieldMaskConfig::remove()];
$fieldPaths = ["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[TestConstants::FIELD_MESSAGE]);
$this->assertSame("ID: " . Mask::MASK_MASKED, $result["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[TestConstants::FIELD_MESSAGE]);
$this->assertSame("No sensitive data here", $result["message"]);
$this->assertSame(self::GDPR_REPLACEMENT, $result["context"]["user"]["ssn"]);
}

View File

@@ -126,6 +126,9 @@ 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(
@@ -155,6 +158,7 @@ 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
@@ -192,6 +196,12 @@ 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']
));
}
/**
@@ -216,27 +226,50 @@ class ComprehensiveValidationTest extends TestCase
];
$caughtCount = 0;
$totalPatterns = count($definitelyDangerousPatterns) + count($possiblyDangerousPatterns);
// Test definitely dangerous patterns
foreach (array_keys($definitelyDangerousPatterns) as $pattern) {
foreach ($definitelyDangerousPatterns as $pattern => $description) {
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 (array_keys($possiblyDangerousPatterns) as $pattern) {
foreach ($possiblyDangerousPatterns as $pattern => $description) {
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));
}
/**
@@ -334,10 +367,17 @@ class ComprehensiveValidationTest extends TestCase
}
if ($sensitiveTermsFound !== []) {
$this->fail(sprintf(
"Scenario '%s': %d sensitive term(s) still present in masked output",
$scenario,
count($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
));
}
@@ -384,6 +424,8 @@ class ComprehensiveValidationTest extends TestCase
if ($json === false) {
$this->fail('RateLimiter::getMemoryStats() returned false');
}
error_log("✅ Rate limiter statistics: " . $json);
}
/**
@@ -442,8 +484,19 @@ 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);
}
}
@@ -475,8 +528,8 @@ class ComprehensiveValidationTest extends TestCase
$processor = $this->createProcessor(
patterns: [
'/\b\d{3}-\d{2}-\d{4}\b/' => MaskConstants::MASK_USSSN,
TestConstants::PATTERN_EMAIL_SIMPLE => MaskConstants::MASK_EMAIL,
TestConstants::PATTERN_CREDIT_CARD => MaskConstants::MASK_CC,
'/[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,
],
fieldPaths: [
TestConstants::FIELD_USER_PASSWORD => FieldMaskConfig::remove(),
@@ -565,6 +618,11 @@ 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"
);
}
/**
@@ -588,6 +646,9 @@ class ComprehensiveValidationTest extends TestCase
PatternValidator::clearCache();
RateLimiter::clearAll();
// Log final validation summary
error_log("🎯 Comprehensive validation completed successfully");
parent::tearDown();
}
}

View File

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

View File

@@ -74,8 +74,8 @@ class SecurityRegressionTest extends TestCase
{
$redosPatterns = [
// Nested quantifiers - classic ReDoS
TestConstants::PATTERN_REDOS_VULNERABLE,
TestConstants::PATTERN_REDOS_NESTED_STAR,
'/^(a+)+$/',
'/^(a*)*$/',
'/^(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',
TestConstants::PATTERN_EMAIL_SIMPLE => MaskConstants::MASK_EMAIL,
TestConstants::PATTERN_CREDIT_CARD => 'CREDIT_CARD',
'/[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',
'/\+?1?[-.\s]?\(?(\d{3})\)?[-.\s]?(\d{3})[-.\s]?(\d{4})/' => 'PHONE',
'/\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b/' => 'IP_ADDRESS',

View File

@@ -7,7 +7,6 @@ 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
@@ -49,9 +48,9 @@ final class RetentionPolicyTest extends TestCase
public function testGetFieldsCustom(): void
{
$policy = new RetentionPolicy('test', 30, RetentionPolicy::ACTION_DELETE, [TestConstants::CONTEXT_EMAIL, 'phone']);
$policy = new RetentionPolicy('test', 30, RetentionPolicy::ACTION_DELETE, ['email', 'phone']);
$this->assertSame([TestConstants::CONTEXT_EMAIL, 'phone'], $policy->getFields());
$this->assertSame(['email', 'phone'], $policy->getFields());
}
public function testIsWithinRetentionRecent(): void
@@ -120,12 +119,12 @@ final class RetentionPolicyTest extends TestCase
public function testAnonymizeFactory(): void
{
$policy = RetentionPolicy::anonymize('user_data', 90, [TestConstants::CONTEXT_EMAIL, 'name']);
$policy = RetentionPolicy::anonymize('user_data', 90, ['email', 'name']);
$this->assertSame('user_data', $policy->getName());
$this->assertSame(90, $policy->getRetentionDays());
$this->assertSame(RetentionPolicy::ACTION_ANONYMIZE, $policy->getAction());
$this->assertSame([TestConstants::CONTEXT_EMAIL, 'name'], $policy->getFields());
$this->assertSame(['email', 'name'], $policy->getFields());
}
public function testActionConstants(): void

View File

@@ -6,7 +6,6 @@ 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;
@@ -315,10 +314,10 @@ class SecuritySanitizerTest extends TestCase
#[Test]
public function preservesPublicIpAddresses(): void
{
$message = 'External server at ' . TestConstants::IP_ADDRESS_PUBLIC . ' responded';
$message = 'External server at 8.8.8.8 responded';
$sanitized = SecuritySanitizer::sanitizeErrorMessage($message);
$this->assertStringContainsString(TestConstants::IP_ADDRESS_PUBLIC, $sanitized);
$this->assertStringContainsString('8.8.8.8', $sanitized);
}
#[Test]

View File

@@ -43,7 +43,7 @@ final class SerializedDataProcessorTest extends TestCase
{
$processor = $this->createProcessor();
$message = 'User data: {"' . TestConstants::CONTEXT_EMAIL . '":"' . TestConstants::EMAIL_JOHN . '","name":"' . TestConstants::NAME_FIRST . '"}';
$message = 'User data: {"email":"' . TestConstants::EMAIL_JOHN . '","name":"John"}';
$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,"' . TestConstants::CONTEXT_EMAIL . '":"' . TestConstants::EMAIL_TEST . '"}';
$message = 'Data: {"id":123,"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":{"' . TestConstants::CONTEXT_EMAIL . '":"' . TestConstants::EMAIL_TEST . '"}}}';
$message = 'User: {"user":{"contact":{"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] => ' . TestConstants::NAME_FULL . '
[' . TestConstants::CONTEXT_EMAIL . '] => ' . TestConstants::EMAIL_JOHN . '
[name] => John Doe
[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(TestConstants::NAME_FULL, $result); // Name not masked
$this->assertStringContainsString('John Doe', $result); // Name not masked
}
public function testProcessPrintROutputWithNestedArrays(): void
@@ -119,8 +119,8 @@ PRINT_R;
$processor = $this->createProcessor();
$varExportOutput = "array (
'name' => '" . TestConstants::NAME_FULL . "',
'" . TestConstants::CONTEXT_EMAIL . "' => '" . TestConstants::EMAIL_JOHN . "',
'name' => 'John Doe',
'email' => '" . TestConstants::EMAIL_JOHN . "',
'active' => true,
)";
@@ -134,7 +134,7 @@ PRINT_R;
{
$processor = $this->createProcessor();
$data = [TestConstants::CONTEXT_EMAIL => TestConstants::EMAIL_TEST, 'name' => 'Test'];
$data = ['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 {"' . TestConstants::CONTEXT_EMAIL . '":"' . TestConstants::EMAIL_TEST . '"} performed action';
$message = 'Log entry: User {"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('{"' . TestConstants::CONTEXT_EMAIL . '":"' . TestConstants::EMAIL_TEST . '"}');
$processor->process('{"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('{"' . TestConstants::CONTEXT_EMAIL . '":"' . TestConstants::EMAIL_TEST . '"}');
$processor->process('{"email":"' . TestConstants::EMAIL_TEST . '"}');
$this->assertNotEmpty($logs);
}
@@ -214,7 +214,7 @@ PRINT_R;
{
$processor = $this->createProcessor();
$message = 'Users: [{"' . TestConstants::CONTEXT_EMAIL . '":"a@example.com"},{"' . TestConstants::CONTEXT_EMAIL . '":"b@example.com"}]';
$message = 'Users: [{"email":"a@example.com"},{"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 (
"' . TestConstants::CONTEXT_EMAIL . '" => "' . TestConstants::EMAIL_JOHN . '",
"email" => "' . TestConstants::EMAIL_JOHN . '",
)';
$result = $processor->process($varExportOutput);
@@ -249,7 +249,7 @@ PRINT_R;
{
$processor = $this->createProcessor();
$message = 'JSON: {"' . TestConstants::CONTEXT_EMAIL . '":"a@example.com"} and serialized: s:16:"b@example.com";';
$message = 'JSON: {"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', TestConstants::MASK_REDACTED_BRACKETS, $value);
$customMasker = fn(string $value): string => str_replace('secret', '[REDACTED]', $value);
$processor = new SerializedDataProcessor($customMasker);
$message = '{"data":"this is secret information"}';
$result = $processor->process($message);
$this->assertStringContainsString(TestConstants::MASK_REDACTED_BRACKETS, $result);
$this->assertStringContainsString('[REDACTED]', $result);
$this->assertStringNotContainsString('secret', $result);
}
}

View File

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

View File

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

View File

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

View File

@@ -146,14 +146,14 @@ final class DataTypeMaskingStrategyComprehensiveTest extends TestCase
public function testMaskWithObjectValueJsonMask(): void
{
$strategy = new DataTypeMaskingStrategy(['object' => '{"' . TestConstants::DATA_MASKED . '":"data"}']);
$strategy = new DataTypeMaskingStrategy(['object' => '{"masked":"data"}']);
$record = $this->createLogRecord('Test');
$obj = (object)['original' => 'value'];
$result = $strategy->mask($obj, 'field', $record);
$expected = (object)[TestConstants::DATA_MASKED => 'data'];
$expected = (object)['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' => '{"' . TestConstants::DATA_MASKED . '":"value"}', // JSON object
'object' => '{"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)[TestConstants::DATA_MASKED => 'value'], $strategy->mask((object)[], 'f', $record));
$this->assertEquals((object)['masked' => 'value'], $strategy->mask((object)[], 'f', $record));
$this->assertSame('N', $strategy->mask(null, 'f', $record));
}
}

View File

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

View File

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

View File

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

View File

@@ -127,11 +127,11 @@ class MaskingStrategiesTest extends TestCase
$this->assertSame(80, $strategy->getPriority());
// Test shouldApply
$this->assertTrue($strategy->shouldApply(TestConstants::EMAIL_JOHN, TestConstants::FIELD_USER_EMAIL, $logRecord));
$this->assertTrue($strategy->shouldApply('john@example.com', TestConstants::FIELD_USER_EMAIL, $logRecord));
$this->assertFalse($strategy->shouldApply('some value', 'other.field', $logRecord));
// Test static replacement
$masked = $strategy->mask(TestConstants::EMAIL_JOHN, TestConstants::FIELD_USER_EMAIL, $logRecord);
$masked = $strategy->mask('john@example.com', 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 TestConstants::STRATEGY_TEST;
return 'Test Strategy';
}
// Expose protected methods for testing

View File

@@ -64,7 +64,7 @@ final class RegexMaskingStrategyTest extends TestCase
$this->expectException(InvalidRegexPatternException::class);
$this->expectExceptionMessage('catastrophic backtracking');
new RegexMaskingStrategy([TestConstants::PATTERN_REDOS_VULNERABLE => MaskConstants::MASK_GENERIC]);
new RegexMaskingStrategy(['/^(a+)+$/' => MaskConstants::MASK_GENERIC]);
}
#[Test]
@@ -124,7 +124,7 @@ final class RegexMaskingStrategyTest extends TestCase
public function maskHandlesArrayValues(): void
{
$strategy = new RegexMaskingStrategy([
'/"' . TestConstants::CONTEXT_EMAIL . '":"[^"]+"/' => '"' . TestConstants::CONTEXT_EMAIL . '":"' . MaskConstants::MASK_EMAIL_PATTERN . '"',
'/"email":"[^"]+"/' => '"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: [TestConstants::FIELD_USER_SSN, 'user.phone']
includePaths: ['user.ssn', 'user.phone']
);
$this->assertTrue($strategy->shouldApply(
TestConstants::DATA_NUMBER_STRING,
TestConstants::FIELD_USER_SSN,
'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, TestConstants::FIELD_USER_SSN, $this->logRecord));
$this->assertTrue($strategy->shouldApply(TestConstants::DATA_NUMBER_STRING, '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,
TestConstants::PATTERN_SAFE => TestConstants::MASK_REDACTED_PLAIN,
'/[a-z]+/' => 'REDACTED',
]);
$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 = [TestConstants::FIELD_USER_SSN];
$includePaths = ['user.ssn'];
$excludePaths = ['debug.*'];
$strategy = new RegexMaskingStrategy(
@@ -334,7 +334,7 @@ final class RegexMaskingStrategyTest extends TestCase
'/REPLACED/' => 'FINAL',
]);
$result = $strategy->mask(TestConstants::VALUE_TEST, 'field', $this->logRecord);
$result = $strategy->mask('test value', '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' => TestConstants::MASK_REDACTED_PLAIN,
'/^line\d+$/m' => 'REDACTED',
]);
$input = "line1\nother\nline2";
$result = $strategy->mask($input, 'field', $this->logRecord);
$this->assertStringContainsString(TestConstants::MASK_REDACTED_PLAIN, $result);
$this->assertStringContainsString('REDACTED', $result);
$this->assertStringContainsString('other', $result);
}
}

View File

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

View File

@@ -122,9 +122,9 @@ final class StrategyManagerComprehensiveTest extends TestCase
$manager = new StrategyManager();
$record = $this->createLogRecord('Test');
$result = $manager->maskValue(TestConstants::VALUE_TEST, 'field', $record);
$result = $manager->maskValue('test value', 'field', $record);
$this->assertSame(TestConstants::VALUE_TEST, $result);
$this->assertSame('test value', $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(TestConstants::MESSAGE_SECRET_DATA, 'field', $record);
$result = $manager->maskValue('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(TestConstants::MESSAGE_SECRET_DATA, 'field', $record);
$result = $manager->hasApplicableStrategy('secret data', 'field', $record);
$this->assertTrue($result);
}

View File

@@ -25,13 +25,13 @@ final class StreamingProcessorTest extends TestCase
$processor = new StreamingProcessor($this->createOrchestrator(), 10);
$records = [
[TestConstants::FIELD_MESSAGE => TestConstants::MESSAGE_TEST_LOWERCASE, 'context' => []],
['message' => 'test message', 'context' => []],
];
$results = iterator_to_array($processor->processStream($records));
$this->assertCount(1, $results);
$this->assertSame(MaskConstants::MASK_GENERIC . ' message', $results[0][TestConstants::FIELD_MESSAGE]);
$this->assertSame(MaskConstants::MASK_GENERIC . ' message', $results[0]['message']);
}
public function testProcessStreamMultipleRecords(): void
@@ -39,9 +39,9 @@ final class StreamingProcessorTest extends TestCase
$processor = new StreamingProcessor($this->createOrchestrator(), 10);
$records = [
[TestConstants::FIELD_MESSAGE => 'test one', 'context' => []],
[TestConstants::FIELD_MESSAGE => 'test two', 'context' => []],
[TestConstants::FIELD_MESSAGE => 'test three', 'context' => []],
['message' => 'test one', 'context' => []],
['message' => 'test two', 'context' => []],
['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 = [
[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' => []],
['message' => 'test 1', 'context' => []],
['message' => 'test 2', 'context' => []],
['message' => 'test 3', 'context' => []],
['message' => 'test 4', 'context' => []],
['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 = [
[TestConstants::FIELD_MESSAGE => TestConstants::FIELD_MESSAGE, 'context' => ['key' => TestConstants::VALUE_TEST]],
['message' => 'message', 'context' => ['key' => 'test value']],
];
$results = iterator_to_array($processor->processStream($records));
$this->assertSame(MaskConstants::MASK_GENERIC . TestConstants::VALUE_SUFFIX, $results[0]['context']['key']);
$this->assertSame(MaskConstants::MASK_GENERIC . ' value', $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 [TestConstants::FIELD_MESSAGE => 'test a', 'context' => []];
yield [TestConstants::FIELD_MESSAGE => 'test b', 'context' => []];
yield [TestConstants::FIELD_MESSAGE => 'test c', 'context' => []];
yield ['message' => 'test a', 'context' => []];
yield ['message' => 'test b', 'context' => []];
yield ['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 => [TestConstants::FIELD_MESSAGE => $line, 'context' => []];
$lineParser = fn(string $line): array => ['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][TestConstants::FIELD_MESSAGE]);
$this->assertStringContainsString(MaskConstants::MASK_GENERIC, $results[0]['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 => [TestConstants::FIELD_MESSAGE => $line, 'context' => []];
$lineParser = fn(string $line): array => ['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 => [TestConstants::FIELD_MESSAGE => $l, 'context' => []]));
iterator_to_array($processor->processFile('/nonexistent/path/file.log', fn(string $l): array => ['message' => $l, 'context' => []]));
}
public function testProcessToFile(): void
@@ -152,15 +152,15 @@ final class StreamingProcessorTest extends TestCase
$processor = new StreamingProcessor($this->createOrchestrator(), 10);
$records = [
[TestConstants::FIELD_MESSAGE => 'test line 1', 'context' => []],
[TestConstants::FIELD_MESSAGE => 'test line 2', 'context' => []],
['message' => 'test line 1', 'context' => []],
['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[TestConstants::FIELD_MESSAGE];
$formatter = fn(array $record): string => $record['message'];
$count = $processor->processToFile($records, $outputFile, $formatter);
$this->assertSame(2, $count);
@@ -188,33 +188,15 @@ final class StreamingProcessorTest extends TestCase
$processor = new StreamingProcessor($this->createOrchestrator(), 10);
$records = [
[TestConstants::FIELD_MESSAGE => 'test masked', 'context' => []],
[TestConstants::FIELD_MESSAGE => 'no sensitive data', 'context' => []],
[TestConstants::FIELD_MESSAGE => 'another test here', 'context' => []],
['message' => 'test masked', 'context' => []],
['message' => 'no sensitive data', 'context' => []],
['message' => 'another test here', 'context' => []],
];
$stats = $processor->getStatistics($records);
$this->assertSame(3, $stats['processed']);
$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);
$this->assertGreaterThan(0, $stats['masked']); // At least some should be masked
}
public function testSetAuditLogger(): void
@@ -227,7 +209,7 @@ final class StreamingProcessorTest extends TestCase
$processor = new StreamingProcessor($this->createOrchestrator(), 1);
$processor->setAuditLogger($auditLogger);
$records = [[TestConstants::FIELD_MESSAGE => 'test', 'context' => []]];
$records = [['message' => 'test', 'context' => []]];
iterator_to_array($processor->processStream($records));
$this->assertNotEmpty($logs);
@@ -253,7 +235,7 @@ final class StreamingProcessorTest extends TestCase
$records = [];
for ($i = 1; $i <= 500; $i++) {
$records[] = [TestConstants::FIELD_MESSAGE => "test record {$i}", 'context' => []];
$records[] = ['message' => "test record {$i}", 'context' => []];
}
$count = 0;

View File

@@ -145,8 +145,6 @@ 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
@@ -170,32 +168,20 @@ 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.