Files
wc-licensed-product-client/tests/Security/StringEncoderTest.php
magdev e87a60926b Add security layer with response signature verification
Security classes:
- ResponseSignature: HMAC-SHA256 signing and verification
- StringEncoder: XOR-based string obfuscation for source code
- IntegrityChecker: Source file hash verification
- SignatureException, IntegrityException for error handling

SecureLicenseClient:
- Verifies server response signatures
- Prevents response tampering and replay attacks
- Per-license derived signing keys
- Optional code integrity checking

Documentation:
- docs/server-implementation.md with complete WordPress/WooCommerce
  integration guide for signing responses

Tests:
- 34 new security tests (66 total, all passing)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-22 16:16:59 +01:00

149 lines
4.0 KiB
PHP

<?php
declare(strict_types=1);
namespace Magdev\WcLicensedProductClient\Tests\Security;
use Magdev\WcLicensedProductClient\Security\StringEncoder;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;
#[CoversClass(StringEncoder::class)]
final class StringEncoderTest extends TestCase
{
#[Test]
public function itEncodesAndDecodesStrings(): void
{
$encoder = new StringEncoder();
$original = 'Hello, World!';
$encoded = $encoder->encode($original);
$decoded = $encoder->decode($encoded);
self::assertSame($original, $decoded);
self::assertNotSame($original, $encoded);
}
#[Test]
public function itEncodesApiPaths(): void
{
$encoder = new StringEncoder();
$path = '/wp-json/wc-licensed-product/v1';
$encoded = $encoder->encode($path);
$decoded = $encoder->decode($encoded);
self::assertSame($path, $decoded);
self::assertStringNotContainsString('wp-json', $encoded);
}
#[Test]
public function itEncodesEndpointNames(): void
{
$encoder = new StringEncoder();
$endpoints = ['validate', 'status', 'activate'];
foreach ($endpoints as $endpoint) {
$encoded = $encoder->encode($endpoint);
$decoded = $encoder->decode($encoded);
self::assertSame($endpoint, $decoded);
}
}
#[Test]
public function itHandlesEmptyString(): void
{
$encoder = new StringEncoder();
$encoded = $encoder->encode('');
$decoded = $encoder->decode($encoded);
self::assertSame('', $decoded);
}
#[Test]
public function itHandlesUnicodeStrings(): void
{
$encoder = new StringEncoder();
$original = 'Ümläut and émojis 🔐';
$encoded = $encoder->encode($original);
$decoded = $encoder->decode($encoded);
self::assertSame($original, $decoded);
}
#[Test]
public function itProducesDifferentOutputWithDifferentKeys(): void
{
$encoder1 = new StringEncoder('key1');
$encoder2 = new StringEncoder('key2');
$original = 'test string';
$encoded1 = $encoder1->encode($original);
$encoded2 = $encoder2->encode($original);
self::assertNotSame($encoded1, $encoded2);
}
#[Test]
public function itRequiresSameKeyForDecoding(): void
{
$encoder1 = new StringEncoder('key1');
$encoder2 = new StringEncoder('key2');
$original = 'test string';
$encoded = $encoder1->encode($original);
$decodedWithWrongKey = $encoder2->decode($encoded);
self::assertNotSame($original, $decodedWithWrongKey);
}
#[Test]
public function itGeneratesEncodedConstants(): void
{
$encoder = new StringEncoder();
$constants = [
'API_PATH' => '/wp-json/wc-licensed-product/v1',
'VALIDATE' => 'validate',
'STATUS' => 'status',
];
$encoded = $encoder->generateEncodedConstants($constants);
self::assertCount(3, $encoded);
self::assertArrayHasKey('API_PATH', $encoded);
self::assertArrayHasKey('VALIDATE', $encoded);
self::assertArrayHasKey('STATUS', $encoded);
// Verify all can be decoded back
foreach ($constants as $name => $value) {
self::assertSame($value, $encoder->decode($encoded[$name]));
}
}
#[Test]
public function itThrowsOnInvalidEncodedString(): void
{
$encoder = new StringEncoder();
$this->expectException(\InvalidArgumentException::class);
$encoder->decode('not-valid-base64!!!');
}
#[Test]
public function itHandlesLongStrings(): void
{
$encoder = new StringEncoder();
$original = str_repeat('A', 10000);
$encoded = $encoder->encode($original);
$decoded = $encoder->decode($encoded);
self::assertSame($original, $decoded);
}
}