Skip to content

Commit

Permalink
Merge pull request #244 from Sammyjo20/feature/v2-fixture-classes
Browse files Browse the repository at this point in the history
Feature | Swap Sensitive Properties Within Fixtures
  • Loading branch information
Sammyjo20 authored Aug 10, 2023
2 parents 5be0efa + eda8c2b commit bee990f
Show file tree
Hide file tree
Showing 11 changed files with 542 additions and 5 deletions.
10 changes: 10 additions & 0 deletions src/Exceptions/FixtureException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

declare(strict_types=1);

namespace Saloon\Exceptions;

class FixtureException extends SaloonException
{
//
}
65 changes: 65 additions & 0 deletions src/Helpers/FixtureHelper.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
<?php

declare(strict_types=1);

namespace Saloon\Helpers;

class FixtureHelper
{
/**
* Recursively replaces array attributes
*
* @param array<string, mixed> $source
* @param array<string, mixed> $rules
* @param bool $caseSensitiveKeys
* @return array<string, mixed>
*/
public static function recursivelyReplaceAttributes(array $source, array $rules, bool $caseSensitiveKeys = true): array
{
if ($caseSensitiveKeys === false) {
$rules = array_change_key_case($rules, CASE_LOWER);
}

array_walk_recursive($source, static function (&$value, $key) use ($rules, $caseSensitiveKeys) {
if ($caseSensitiveKeys === false) {
$key = mb_strtolower($key);
}

if (! array_key_exists($key, $rules)) {
return;
}

$swappedValue = $rules[$key];

$value = is_callable($swappedValue) ? $swappedValue($value) : $swappedValue;
});

return $source;
}

/**
* Replace sensitive regex patterns
*
* @param string $source
* @param array<string, string> $patterns
* @return string
*/
public static function replaceSensitiveRegexPatterns(string $source, array $patterns): string
{
foreach ($patterns as $pattern => $replacement) {
$matches = [];

preg_match_all($pattern, $source, $matches);

$matches = array_unique($matches[0] ?? []);

foreach ($matches as $match) {
$value = is_callable($replacement) ? $replacement($match) : $replacement;

$source = str_replace($match, $value, $source);
}
}

return $source;
}
}
147 changes: 142 additions & 5 deletions src/Http/Faking/Fixture.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
use Saloon\Helpers\Storage;
use Saloon\Helpers\MockConfig;
use Saloon\Data\RecordedResponse;
use Saloon\Helpers\FixtureHelper;
use Saloon\Exceptions\FixtureException;
use Saloon\Exceptions\FixtureMissingException;

class Fixture
Expand All @@ -23,7 +25,7 @@ class Fixture
*
* @var string
*/
protected string $name;
protected string $name = '';

/**
* The storage helper
Expand All @@ -40,7 +42,7 @@ class Fixture
* @throws \Saloon\Exceptions\DirectoryNotFoundException
* @throws \Saloon\Exceptions\UnableToCreateDirectoryException
*/
public function __construct(string $name, Storage $storage = null)
public function __construct(string $name = '', Storage $storage = null)
{
$this->name = $name;
$this->storage = $storage ?? new Storage(MockConfig::getFixturePath(), true);
Expand All @@ -51,7 +53,7 @@ public function __construct(string $name, Storage $storage = null)
*
* @return \Saloon\Http\Faking\MockResponse|null
* @throws \Saloon\Exceptions\FixtureMissingException
* @throws \JsonException
* @throws \JsonException|\Saloon\Exceptions\FixtureException
*/
public function getMockResponse(): ?MockResponse
{
Expand All @@ -76,10 +78,15 @@ public function getMockResponse(): ?MockResponse
* @return $this
* @throws \JsonException
* @throws \Saloon\Exceptions\UnableToCreateDirectoryException
* @throws \Saloon\Exceptions\UnableToCreateFileException
* @throws \Saloon\Exceptions\UnableToCreateFileException|\Saloon\Exceptions\FixtureException
*/
public function store(RecordedResponse $recordedResponse): static
{
$recordedResponse = $this->swapSensitiveHeaders($recordedResponse);
$recordedResponse = $this->swapSensitiveJson($recordedResponse);
$recordedResponse = $this->swapSensitiveBodyWithRegex($recordedResponse);
$recordedResponse = $this->beforeSave($recordedResponse);

$this->storage->put($this->getFixturePath(), $recordedResponse->toFile());

return $this;
Expand All @@ -89,9 +96,139 @@ public function store(RecordedResponse $recordedResponse): static
* Get the fixture path
*
* @return string
* @throws \Saloon\Exceptions\FixtureException
*/
public function getFixturePath(): string
{
return sprintf('%s.%s', $this->name, $this::$fixtureExtension);
$name = $this->name;

if (empty($name)) {
$name = $this->defineName();
}

if (empty($name)) {
throw new FixtureException('The fixture must have a name');
}

return sprintf('%s.%s', $name, $this::$fixtureExtension);
}

/**
* Define the fixture name
*
* @return string
*/
protected function defineName(): string
{
return '';
}

/**
* Swap any sensitive headers
*
* @param \Saloon\Data\RecordedResponse $recordedResponse
* @return \Saloon\Data\RecordedResponse
*/
protected function swapSensitiveHeaders(RecordedResponse $recordedResponse): RecordedResponse
{
$sensitiveHeaders = $this->defineSensitiveHeaders();

if (empty($sensitiveHeaders)) {
return $recordedResponse;
}

$recordedResponse->headers = FixtureHelper::recursivelyReplaceAttributes($recordedResponse->headers, $sensitiveHeaders, false);

return $recordedResponse;
}

/**
* Swap any sensitive JSON data
*
* @param \Saloon\Data\RecordedResponse $recordedResponse
* @return \Saloon\Data\RecordedResponse
* @throws \JsonException
*/
protected function swapSensitiveJson(RecordedResponse $recordedResponse): RecordedResponse
{
$body = json_decode($recordedResponse->data, true);

if (empty($body) || json_last_error() !== JSON_ERROR_NONE) {
return $recordedResponse;
}

$sensitiveJsonParameters = $this->defineSensitiveJsonParameters();

if (empty($sensitiveJsonParameters)) {
return $recordedResponse;
}

$redactedData = FixtureHelper::recursivelyReplaceAttributes($body, $sensitiveJsonParameters);

$recordedResponse->data = json_encode($redactedData, JSON_THROW_ON_ERROR);

return $recordedResponse;
}

/**
* Swap sensitive body with regex patterns
*
* @param \Saloon\Data\RecordedResponse $recordedResponse
* @return \Saloon\Data\RecordedResponse
*/
protected function swapSensitiveBodyWithRegex(RecordedResponse $recordedResponse): RecordedResponse
{
$sensitiveRegexPatterns = $this->defineSensitiveRegexPatterns();

if (empty($sensitiveRegexPatterns)) {
return $recordedResponse;
}

$redactedData = FixtureHelper::replaceSensitiveRegexPatterns($recordedResponse->data, $sensitiveRegexPatterns);

$recordedResponse->data = $redactedData;

return $recordedResponse;
}

/**
* Swap any sensitive headers
*
* @return array<string, string|callable>
*/
protected function defineSensitiveHeaders(): array
{
return [];
}

/**
* Swap any sensitive JSON parameters
*
* @return array<string, string|callable>
*/
protected function defineSensitiveJsonParameters(): array
{
return [];
}

/**
* Define regex patterns that should be replaced
*
* @return array<string, string>
*/
protected function defineSensitiveRegexPatterns(): array
{
return [];
}

/**
* Hook to use before saving
*
* @param \Saloon\Data\RecordedResponse $recordedResponse
* @return \Saloon\Data\RecordedResponse
*/
protected function beforeSave(RecordedResponse $recordedResponse): RecordedResponse
{
return $recordedResponse;
}
}
1 change: 1 addition & 0 deletions src/Http/Faking/SimulatedResponsePayload.php
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ public function __construct(array|string $body = [], int $status = 200, array $h
* @param string $name
* @return \Saloon\Http\Faking\Fixture
* @throws \Saloon\Exceptions\DirectoryNotFoundException
* @throws \Saloon\Exceptions\UnableToCreateDirectoryException
*/
public static function fixture(string $name): Fixture
{
Expand Down
Loading

0 comments on commit bee990f

Please sign in to comment.